diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java index b6a5e7e8de9..b6893c13e14 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java @@ -209,7 +209,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { for (int i = 0; i < theRequest.getEntry().size(); i++) { if (i % 100 == 0) { - ourLog.info("Processed {} non-GET entries out of {}", i, theRequest.getEntry().size()); + ourLog.debug("Processed {} non-GET entries out of {}", i, theRequest.getEntry().size()); } Entry nextReqEntry = theRequest.getEntry().get(i); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index de3746e753a..e4e3cf21a70 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -815,7 +815,7 @@ public class SearchBuilder implements ISearchBuilder { continue; } - predicate = join.get("myUri").as(String.class).in(toFind); + predicate = join.get("myUri").as(String.class).in(toFind); } else if (param.getQualifier() == UriParamQualifierEnum.BELOW) { predicate = myBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value)); @@ -953,8 +953,8 @@ public class SearchBuilder implements ISearchBuilder { Predicate lb = null; if (lowerBound != null) { - Predicate gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBound); - Predicate lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), lowerBound); + Predicate gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBound); + Predicate lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), lowerBound); if (theRange.getLowerBound().getPrefix() == ParamPrefixEnum.STARTS_AFTER || theRange.getLowerBound().getPrefix() == ParamPrefixEnum.EQUAL) { lb = gt; } else { @@ -964,8 +964,8 @@ public class SearchBuilder implements ISearchBuilder { Predicate ub = null; if (upperBound != null) { - Predicate gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), upperBound); - Predicate lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBound); + Predicate gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), upperBound); + Predicate lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBound); if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) { ub = lt; } else { @@ -2008,15 +2008,15 @@ public class SearchBuilder implements ISearchBuilder { } private static List createLastUpdatedPredicates(final DateRangeParam theLastUpdated, CriteriaBuilder builder, From from) { - List lastUpdatedPredicates = new ArrayList(); + List lastUpdatedPredicates = new ArrayList<>(); if (theLastUpdated != null) { if (theLastUpdated.getLowerBoundAsInstant() != null) { ourLog.debug("LastUpdated lower bound: {}", new InstantDt(theLastUpdated.getLowerBoundAsInstant())); - Predicate predicateLower = builder.greaterThanOrEqualTo(from.get("myUpdated"), theLastUpdated.getLowerBoundAsInstant()); + Predicate predicateLower = builder.greaterThanOrEqualTo(from.get("myUpdated"), theLastUpdated.getLowerBoundAsInstant()); lastUpdatedPredicates.add(predicateLower); } if (theLastUpdated.getUpperBoundAsInstant() != null) { - Predicate predicateUpper = builder.lessThanOrEqualTo(from.get("myUpdated"), theLastUpdated.getUpperBoundAsInstant()); + Predicate predicateUpper = builder.lessThanOrEqualTo(from.get("myUpdated"), theLastUpdated.getUpperBoundAsInstant()); lastUpdatedPredicates.add(predicateUpper); } } @@ -2262,10 +2262,7 @@ public class SearchBuilder implements ISearchBuilder { if (myNext == null) { fetchNext(); } - if (myNext == NO_MORE) { - return false; - } - return true; + return myNext != NO_MORE; } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java new file mode 100644 index 00000000000..4be607a9390 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java @@ -0,0 +1,28 @@ +package ca.uhn.fhir.jpa.dao.data; + +import ca.uhn.fhir.jpa.entity.TermConceptDesignation; +import org.springframework.data.jpa.repository.JpaRepository; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2018 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +public interface ITermConceptDesignationDao extends JpaRepository { + // nothing +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java index 19ac3bf479a..3bbb31c17e2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java @@ -318,7 +318,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { for (int i = 0; i < theEntries.size(); i++) { if (i % 100 == 0) { - ourLog.info("Processed {} non-GET entries out of {}", i, theEntries.size()); + ourLog.debug("Processed {} non-GET entries out of {}", i, theEntries.size()); } BundleEntryComponent nextReqEntry = theEntries.get(i); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java index 04e42e6d6e4..fa241a6b37e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java @@ -333,7 +333,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao { for (int i = 0; i < theEntries.size(); i++) { if (i % 100 == 0) { - ourLog.info("Processed {} non-GET entries out of {}", i, theEntries.size()); + ourLog.debug("Processed {} non-GET entries out of {}", i, theEntries.size()); } BundleEntryComponent nextReqEntry = theEntries.get(i); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java index 01c1ea0d511..96f2efb1109 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java @@ -81,6 +81,9 @@ public class TermConcept implements Serializable { @FieldBridge(impl = TermConceptPropertyFieldBridge.class) private Collection myProperties; + @OneToMany(mappedBy = "myConcept", orphanRemoval = true) + private Collection myDesignations; + @Id() @SequenceGenerator(name = "SEQ_CONCEPT_PID", sequenceName = "SEQ_CONCEPT_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PID") @@ -123,6 +126,13 @@ public class TermConcept implements Serializable { } } + public TermConceptDesignation addDesignation() { + TermConceptDesignation designation = new TermConceptDesignation(); + designation.setConcept(this); + getDesignations().add(designation); + return designation; + } + private TermConceptProperty addProperty(@Nonnull TermConceptPropertyTypeEnum thePropertyType, @Nonnull String thePropertyName, @Nonnull String thePropertyValue) { Validate.notBlank(thePropertyName); @@ -189,6 +199,29 @@ public class TermConcept implements Serializable { } } + public List getCodingProperties(String thePropertyName) { + List retVal = new ArrayList<>(); + for (TermConceptProperty next : getProperties()) { + if (thePropertyName.equals(next.getKey())) { + if (next.getType() == TermConceptPropertyTypeEnum.CODING) { + Coding coding = new Coding(); + coding.setSystem(next.getCodeSystem()); + coding.setCode(next.getValue()); + coding.setDisplay(next.getDisplay()); + retVal.add(coding); + } + } + } + return retVal; + } + + public Collection getDesignations() { + if (myDesignations == null) { + myDesignations = new ArrayList<>(); + } + return myDesignations; + } + public String getDisplay() { return myDisplay; } @@ -231,6 +264,14 @@ public class TermConcept implements Serializable { return myProperties; } + public Integer getSequence() { + return mySequence; + } + + public void setSequence(Integer theSequence) { + mySequence = theSequence; + } + public List getStringProperties(String thePropertyName) { List retVal = new ArrayList<>(); for (TermConceptProperty next : getProperties()) { @@ -243,30 +284,6 @@ public class TermConcept implements Serializable { return retVal; } - public List getCodingProperties(String thePropertyName) { - List retVal = new ArrayList<>(); - for (TermConceptProperty next : getProperties()) { - if (thePropertyName.equals(next.getKey())) { - if (next.getType() == TermConceptPropertyTypeEnum.CODING) { - Coding coding = new Coding(); - coding.setSystem(next.getCodeSystem()); - coding.setCode(next.getValue()); - coding.setDisplay(next.getDisplay()); - retVal.add(coding); - } - } - } - return retVal; - } - - public Integer getSequence() { - return mySequence; - } - - public void setSequence(Integer theSequence) { - mySequence = theSequence; - } - public String getStringProperty(String thePropertyName) { List properties = getStringProperties(thePropertyName); if (properties.size() > 0) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java new file mode 100644 index 00000000000..0bdaa4ad492 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java @@ -0,0 +1,103 @@ +package ca.uhn.fhir.jpa.entity; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2018 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import javax.persistence.*; +import java.io.Serializable; + +@Entity +@Table(name = "TRM_CONCEPT_DESIG", uniqueConstraints = { +}, indexes = { +}) +public class TermConceptDesignation implements Serializable { + + private static final long serialVersionUID = 1L; + @ManyToOne + @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CONCEPT")) + private TermConcept myConcept; + @Id() + @SequenceGenerator(name = "SEQ_CONCEPT_DESIG_PID", sequenceName = "SEQ_CONCEPT_DESIG_PID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_DESIG_PID") + @Column(name = "PID") + private Long myId; + @Column(name = "LANG", length = 500, nullable = true) + private String myLanguage; + @Column(name = "USE_SYSTEM", length = 500, nullable = true) + private String myUseSystem; + @Column(name = "USE_CODE", length = 500, nullable = true) + private String myUseCode; + @Column(name = "USE_DISPLAY", length = 500, nullable = true) + private String myUseDisplay; + @Column(name = "VAL", length = 500, nullable = false) + private String myValue; + + public String getLanguage() { + return myLanguage; + } + + public TermConceptDesignation setLanguage(String theLanguage) { + myLanguage = theLanguage; + return this; + } + + public String getUseCode() { + return myUseCode; + } + + public TermConceptDesignation setUseCode(String theUseCode) { + myUseCode = theUseCode; + return this; + } + + public String getUseDisplay() { + return myUseDisplay; + } + + public TermConceptDesignation setUseDisplay(String theUseDisplay) { + myUseDisplay = theUseDisplay; + return this; + } + + public String getUseSystem() { + return myUseSystem; + } + + public TermConceptDesignation setUseSystem(String theUseSystem) { + myUseSystem = theUseSystem; + return this; + } + + public String getValue() { + return myValue; + } + + public TermConceptDesignation setValue(String theValue) { + myValue = theValue; + return this; + } + + public TermConceptDesignation setConcept(TermConcept theConcept) { + myConcept = theConcept; + return this; + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java index 852141838eb..71f8c985d9d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java @@ -43,22 +43,22 @@ public class TermConceptProperty implements Serializable { @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PROP_PID") @Column(name = "PID") private Long myId; - @Column(name = "PROP_KEY", length = 200, nullable = false) + @Column(name = "PROP_KEY", length = 500, nullable = false) @NotBlank private String myKey; - @Column(name = "PROP_VAL", length = 200, nullable = true) + @Column(name = "PROP_VAL", length = 500, nullable = true) private String myValue; @Column(name = "PROP_TYPE", length = MAX_PROPTYPE_ENUM_LENGTH, nullable = false) private TermConceptPropertyTypeEnum myType; /** * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ - @Column(name = "PROP_CODESYSTEM", length = 200, nullable = true) + @Column(name = "PROP_CODESYSTEM", length = 500, nullable = true) private String myCodeSystem; /** * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ - @Column(name = "PROP_DISPLAY", length = 200, nullable = true) + @Column(name = "PROP_DISPLAY", length = 500, nullable = true) private String myDisplay; /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java index a22396e22ff..c427ed579a9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java @@ -168,6 +168,19 @@ public abstract class BaseSubscriptionInterceptor exten retVal.getEmailDetails().setSubjectTemplate(subjectTemplate); } + if (retVal.getChannelType() == Subscription.SubscriptionChannelType.RESTHOOK) { + String stripVersionIds; + String deliverLatestVersion; + try { + stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS); + deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION); + } catch (FHIRException theE) { + throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); + } + retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds)); + retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion)); + } + } catch (FHIRException theE) { throw new InternalErrorException(theE); } @@ -201,6 +214,19 @@ public abstract class BaseSubscriptionInterceptor exten retVal.getEmailDetails().setSubjectTemplate(subjectTemplate); } + if (retVal.getChannelType() == Subscription.SubscriptionChannelType.RESTHOOK) { + String stripVersionIds; + String deliverLatestVersion; + try { + stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS); + deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION); + } catch (FHIRException theE) { + throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); + } + retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds)); + retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion)); + } + List topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics"); if (topicExts.size() > 0) { IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java index 0c54a644790..aa75ce9dfb6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java @@ -67,6 +67,8 @@ public class CanonicalSubscription implements Serializable { private CanonicalEventDefinition myTrigger; @JsonProperty("emailDetails") private EmailDetails myEmailDetails; + @JsonProperty("restHookDetails") + private RestHookDetails myRestHookDetails; /** * For now we're using the R4 TriggerDefinition, but this @@ -131,12 +133,10 @@ public class CanonicalSubscription implements Serializable { return myHeaders; } - public void setHeaders(List> theHeader) { + public void setHeaders(String theHeaders) { myHeaders = new ArrayList<>(); - for (IPrimitiveType next : theHeader) { - if (isNotBlank(next.getValueAsString())) { - myHeaders.add(next.getValueAsString()); - } + if (isNotBlank(theHeaders)) { + myHeaders.add(theHeaders); } } @@ -160,6 +160,13 @@ public class CanonicalSubscription implements Serializable { myPayloadString = thePayloadString; } + public RestHookDetails getRestHookDetails() { + if (myRestHookDetails == null) { + myRestHookDetails = new RestHookDetails(); + } + return myRestHookDetails; + } + public Subscription.SubscriptionStatus getStatus() { return myStatus; } @@ -191,10 +198,12 @@ public class CanonicalSubscription implements Serializable { } } - public void setHeaders(String theHeaders) { + public void setHeaders(List> theHeader) { myHeaders = new ArrayList<>(); - if (isNotBlank(theHeaders)) { - myHeaders.add(theHeaders); + for (IPrimitiveType next : theHeader) { + if (isNotBlank(next.getValueAsString())) { + myHeaders.add(next.getValueAsString()); + } } } @@ -230,6 +239,32 @@ public class CanonicalSubscription implements Serializable { } } + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) + public static class RestHookDetails { + @JsonProperty("stripVersionId") + private boolean myStripVersionId; + @JsonProperty("deliverLatestVersion") + private boolean myDeliverLatestVersion; + + public boolean isDeliverLatestVersion() { + return myDeliverLatestVersion; + } + + public void setDeliverLatestVersion(boolean theDeliverLatestVersion) { + myDeliverLatestVersion = theDeliverLatestVersion; + } + + public boolean isStripVersionId() { + return myStripVersionId; + } + + public void setStripVersionId(boolean theStripVersionId) { + myStripVersionId = theStripVersionId; + } + + } + @JsonInclude(JsonInclude.Include.NON_NULL) @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) public static class CanonicalEventDefinition { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java index a04b44af8c1..65881b7d2b3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java @@ -28,8 +28,10 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.api.*; import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; import ca.uhn.fhir.rest.gclient.IClientExecutable; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,21 +53,26 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe } protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) { - IBaseResource payloadResource = theMsg.getPayload(getContext()); + IBaseResource payloadResource = getAndMassagePayload(theMsg, theSubscription); + if (payloadResource == null) return; + doDelivery(theMsg, theSubscription, thePayloadType, theClient, payloadResource); + } + + protected void doDelivery(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient, IBaseResource thePayloadResource) { IClientExecutable operation; switch (theMsg.getOperationType()) { case CREATE: - if (payloadResource == null || payloadResource.isEmpty()) { + if (thePayloadResource == null || thePayloadResource.isEmpty()) { if (thePayloadType != null ) { - operation = theClient.create().resource(payloadResource); + operation = theClient.create().resource(thePayloadResource); } else { sendNotification(theMsg); return; } } else { if (thePayloadType != null ) { - operation = theClient.update().resource(payloadResource); + operation = theClient.update().resource(thePayloadResource); } else { sendNotification(theMsg); return; @@ -73,16 +80,16 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe } break; case UPDATE: - if (payloadResource == null || payloadResource.isEmpty()) { + if (thePayloadResource == null || thePayloadResource.isEmpty()) { if (thePayloadType != null ) { - operation = theClient.create().resource(payloadResource); + operation = theClient.create().resource(thePayloadResource); } else { sendNotification(theMsg); return; } } else { if (thePayloadType != null ) { - operation = theClient.update().resource(payloadResource); + operation = theClient.update().resource(thePayloadResource); } else { sendNotification(theMsg); return; @@ -101,7 +108,7 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe operation.encoded(thePayloadType); } - ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), payloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue()); + ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), thePayloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue()); try { operation.execute(); @@ -112,6 +119,27 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe } } + protected IBaseResource getAndMassagePayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription) { + IBaseResource payloadResource = theMsg.getPayload(getContext()); + + if (theSubscription.getRestHookDetails().isDeliverLatestVersion()) { + IFhirResourceDao dao = getSubscriptionDao().getDao(payloadResource.getClass()); + try { + payloadResource = dao.read(payloadResource.getIdElement().toVersionless()); + } catch (ResourceGoneException e) { + ourLog.warn("Resource {} is deleted, not going to deliver for subscription {}", payloadResource.getIdElement(), theSubscription.getIdElement(getContext())); + return null; + } + } + + IIdType resourceId = payloadResource.getIdElement(); + if (theSubscription.getRestHookDetails().isStripVersionId()) { + resourceId = resourceId.toVersionless(); + payloadResource.setId(resourceId); + } + return payloadResource; + } + @Override public void handleMessage(ResourceDeliveryMessage theMessage) throws MessagingException { CanonicalSubscription subscription = theMessage.getSubscription(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java index c62b62cd72f..80330fcc85e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java @@ -63,6 +63,7 @@ import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -78,6 +79,8 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc @Autowired protected ITermConceptPropertyDao myConceptPropertyDao; @Autowired + protected ITermConceptDesignationDao myConceptDesignationDao; + @Autowired protected FhirContext myContext; @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; @@ -87,7 +90,9 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc private List myConceptLinksToSaveLater = new ArrayList<>(); @Autowired private ITermConceptParentChildLinkDao myConceptParentChildLinkDao; - private List myConceptsToSaveLater = new ArrayList<>(); + private List myDeferredConcepts = Collections.synchronizedList(new ArrayList<>()); + private List myDeferredValueSets = Collections.synchronizedList(new ArrayList<>()); + private List myDeferredConceptMaps = Collections.synchronizedList(new ArrayList<>()); @Autowired private DaoConfig myDaoConfig; private long myNextReindexPass; @@ -103,6 +108,15 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc contains.setCode(theConcept.getCode()); contains.setSystem(theCodeSystem); contains.setDisplay(theConcept.getDisplay()); + for (TermConceptDesignation nextDesignation : theConcept.getDesignations()) { + contains + .addDesignation() + .setValue(nextDesignation.getValue()) + .getUse() + .setSystem(nextDesignation.getUseSystem()) + .setCode(nextDesignation.getUseCode()) + .setDisplay(nextDesignation.getUseDisplay()); + } } } @@ -146,11 +160,11 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc return retVal; } - protected abstract IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource, RequestDetails theRequestDetails); + protected abstract IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource); - protected abstract void createOrUpdateConceptMap(ConceptMap theNextConceptMap, RequestDetails theRequestDetails); + protected abstract void createOrUpdateConceptMap(ConceptMap theNextConceptMap); - abstract void createOrUpdateValueSet(ValueSet theValueSet, RequestDetails theRequestDetails); + abstract void createOrUpdateValueSet(ValueSet theValueSet); @Override public void deleteCodeSystem(TermCodeSystem theCodeSystem) { @@ -162,10 +176,12 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc myCodeSystemDao.save(cs); myCodeSystemDao.flush(); + int i = 0; for (TermCodeSystemVersion next : myCodeSystemVersionDao.findByCodeSystemResource(theCodeSystem.getPid())) { myConceptParentChildLinkDao.deleteByCodeSystemVersion(next.getPid()); for (TermConcept nextConcept : myConceptDao.findByCodeSystemVersion(next.getPid())) { myConceptPropertyDao.delete(nextConcept.getProperties()); + myConceptDesignationDao.delete(nextConcept.getDesignations()); myConceptDao.delete(nextConcept); } if (next.getCodeSystem().getCurrentVersion() == next) { @@ -173,10 +189,15 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc myCodeSystemDao.save(next.getCodeSystem()); } myCodeSystemVersionDao.delete(next); + + if (i++ % 1000 == 0) { + myEntityManager.flush(); + } } myCodeSystemVersionDao.deleteForCodeSystem(theCodeSystem); myCodeSystemDao.delete(theCodeSystem); + myEntityManager.flush(); } private int ensureParentsSaved(Collection theParents) { @@ -223,11 +244,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc haveIncludeCriteria = true; TermConcept code = findCode(system, nextCode); if (code != null) { - addedCodes.add(nextCode); - ValueSet.ValueSetExpansionContainsComponent contains = expansionComponent.addContains(); - contains.setCode(nextCode); - contains.setSystem(system); - contains.setDisplay(code.getDisplay()); + addCodeIfNotAlreadyAdded(system, expansionComponent, addedCodes, code); } } } @@ -492,7 +509,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc if (theConceptsStack.size() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) { saveConcept(theConcept); } else { - myConceptsToSaveLater.add(theConcept); + myDeferredConcepts.add(theConcept); } for (TermConceptParentChildLink next : theConcept.getChildren()) { @@ -507,10 +524,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc } } - for (TermConceptProperty next : theConcept.getProperties()) { - myConceptPropertyDao.save(next); - } - } private void populateVersion(TermConcept theNext, TermCodeSystemVersion theCodeSystemVersion) { @@ -523,20 +536,30 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc } } + private void processDeferredConceptMaps() { + int count = Math.min(myDeferredConceptMaps.size(), 5); + for (ConceptMap nextConceptMap : new ArrayList<>(myDeferredConceptMaps.subList(0, count))) { + ourLog.info("Creating ConceptMap: {}", nextConceptMap.getId()); + createOrUpdateConceptMap(nextConceptMap); + myDeferredConceptMaps.remove(nextConceptMap); + } + ourLog.info("Saved {} deferred ConceptMap resources, have {} remaining", count, myDeferredConceptMaps.size()); + } + private void processDeferredConcepts() { int codeCount = 0, relCount = 0; StopWatch stopwatch = new StopWatch(); - int count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myConceptsToSaveLater.size()); + int count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myDeferredConcepts.size()); ourLog.info("Saving {} deferred concepts...", count); - while (codeCount < count && myConceptsToSaveLater.size() > 0) { - TermConcept next = myConceptsToSaveLater.remove(0); + while (codeCount < count && myDeferredConcepts.size() > 0) { + TermConcept next = myDeferredConcepts.remove(0); codeCount += saveConcept(next); } if (codeCount > 0) { ourLog.info("Saved {} deferred concepts ({} codes remain and {} relationships remain) in {}ms ({}ms / code)", - new Object[] {codeCount, myConceptsToSaveLater.size(), myConceptLinksToSaveLater.size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(codeCount)}); + new Object[] {codeCount, myDeferredConcepts.size(), myConceptLinksToSaveLater.size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(codeCount)}); } if (codeCount == 0) { @@ -560,11 +583,21 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc new Object[] {relCount, myConceptLinksToSaveLater.size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(codeCount)}); } - if ((myConceptsToSaveLater.size() + myConceptLinksToSaveLater.size()) == 0) { + if ((myDeferredConcepts.size() + myConceptLinksToSaveLater.size()) == 0) { ourLog.info("All deferred concepts and relationships have now been synchronized to the database"); } } + private void processDeferredValueSets() { + int count = Math.min(myDeferredValueSets.size(), 5); + for (ValueSet nextValueSet : new ArrayList<>(myDeferredValueSets.subList(0, count))) { + ourLog.info("Creating ValueSet: {}", nextValueSet.getId()); + createOrUpdateValueSet(nextValueSet); + myDeferredValueSets.remove(nextValueSet); + } + ourLog.info("Saved {} deferred ValueSet resources, have {} remaining", count, myDeferredConceptMaps.size()); + } + private void processReindexing() { if (System.currentTimeMillis() < myNextReindexPass && !ourForceSaveDeferredAlwaysForUnitTest) { return; @@ -656,6 +689,14 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc retVal++; theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED); myConceptDao.save(theConcept); + + for (TermConceptProperty next : theConcept.getProperties()) { + myConceptPropertyDao.save(next); + } + + for (TermConceptDesignation next : theConcept.getDesignations()) { + myConceptDesignationDao.save(next); + } } ourLog.trace("Saved {} and got PID {}", theConcept.getCode(), theConcept.getId()); @@ -674,20 +715,31 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc public synchronized void saveDeferred() { if (!myProcessDeferred) { return; - } else if (myConceptsToSaveLater.isEmpty() && myConceptLinksToSaveLater.isEmpty()) { + } else if (myDeferredConcepts.isEmpty() && myConceptLinksToSaveLater.isEmpty()) { processReindexing(); return; } TransactionTemplate tt = new TransactionTemplate(myTransactionMgr); tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus theArg0) { - processDeferredConcepts(); - } - + tt.execute(t -> { + processDeferredConcepts(); + return null; }); + + if (myDeferredValueSets.size() > 0) { + tt.execute(t -> { + processDeferredValueSets(); + return null; + }); + } + if (myDeferredConceptMaps.size() > 0) { + tt.execute(t -> { + processDeferredConceptMaps(); + return null; + }); + } + } @Override @@ -754,8 +806,8 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc ourLog.info("Validating all codes in CodeSystem for storage (this can take some time for large sets)"); // Validate the code system - ArrayList conceptsStack = new ArrayList(); - IdentityHashMap allConcepts = new IdentityHashMap(); + ArrayList conceptsStack = new ArrayList<>(); + IdentityHashMap allConcepts = new IdentityHashMap<>(); int totalCodeCount = 0; for (TermConcept next : theCodeSystemVersion.getConcepts()) { totalCodeCount += validateConceptForStorage(next, theCodeSystemVersion, conceptsStack, allConcepts); @@ -790,8 +842,8 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc ourLog.info("Done deleting old code system versions"); - if (myConceptsToSaveLater.size() > 0 || myConceptLinksToSaveLater.size() > 0) { - ourLog.info("Note that some concept saving was deferred - still have {} concepts and {} relationships", myConceptsToSaveLater.size(), myConceptLinksToSaveLater.size()); + if (myDeferredConcepts.size() > 0 || myConceptLinksToSaveLater.size() > 0) { + ourLog.info("Note that some concept saving was deferred - still have {} concepts and {} relationships", myDeferredConcepts.size(), myConceptLinksToSaveLater.size()); } } @@ -800,7 +852,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc public void storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets, List theConceptMaps) { Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); - IIdType csId = createOrUpdateCodeSystem(theCodeSystemResource, theRequestDetails); + IIdType csId = createOrUpdateCodeSystem(theCodeSystemResource); ResourceTable resource = (ResourceTable) myCodeSystemResourceDao.readEntity(csId); Long codeSystemResourcePid = resource.getId(); @@ -810,14 +862,8 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc theCodeSystemVersion.setResource(resource); storeNewCodeSystemVersion(codeSystemResourcePid, theCodeSystemResource.getUrl(), theCodeSystemResource.getName(), theCodeSystemVersion); - for (ValueSet nextValueSet : theValueSets) { - createOrUpdateValueSet(nextValueSet, theRequestDetails); - } - - for (ConceptMap nextConceptMap : theConceptMaps) { - createOrUpdateConceptMap(nextConceptMap, theRequestDetails); - } - + myDeferredConceptMaps.addAll(theConceptMaps); + myDeferredValueSets.addAll(theValueSets); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java index ce5a4d4f80b..8d4a6e056e5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java @@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.term; * #L% */ -import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.hapi.validation.IValidationSupport; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.CodeSystem; @@ -62,17 +61,17 @@ public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl { } @Override - protected IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource, RequestDetails theRequestDetails) { + protected IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource) { throw new UnsupportedOperationException(); } @Override - protected void createOrUpdateConceptMap(ConceptMap theNextConceptMap, RequestDetails theRequestDetails) { + protected void createOrUpdateConceptMap(ConceptMap theNextConceptMap) { throw new UnsupportedOperationException(); } @Override - protected void createOrUpdateValueSet(ValueSet theValueSet, RequestDetails theRequestDetails) { + protected void createOrUpdateValueSet(ValueSet theValueSet) { throw new UnsupportedOperationException(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java index 67b8c86dcb1..d19a864aa2f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java @@ -5,7 +5,6 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.UrlUtil; @@ -29,6 +28,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; /* @@ -103,39 +103,52 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen } @Override - protected IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource, RequestDetails theRequestDetails) { - String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); + protected IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource) { CodeSystem resourceToStore; try { resourceToStore = VersionConvertor_30_40.convertCodeSystem(theCodeSystemResource); } catch (FHIRException e) { throw new InternalErrorException(e); } - return myCodeSystemResourceDao.update(resourceToStore, matchUrl, theRequestDetails).getId(); + if (isBlank(resourceToStore.getIdElement().getIdPart())) { + String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); + return myCodeSystemResourceDao.update(resourceToStore, matchUrl).getId(); + } else { + return myCodeSystemResourceDao.update(resourceToStore).getId(); + } } @Override - protected void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap, RequestDetails theRequestDetails) { - String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); + protected void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap) { ConceptMap resourceToStore; try { resourceToStore = VersionConvertor_30_40.convertConceptMap(theConceptMap); } catch (FHIRException e) { throw new InternalErrorException(e); } - myConceptMapResourceDao.update(resourceToStore, matchUrl, theRequestDetails).getId(); + if (isBlank(resourceToStore.getIdElement().getIdPart())) { + String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); + myConceptMapResourceDao.update(resourceToStore, matchUrl); + } else { + myConceptMapResourceDao.update(resourceToStore); + } } @Override - protected void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet, RequestDetails theRequestDetails) { - String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); + protected void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet) { ValueSet valueSetDstu3; try { valueSetDstu3 = VersionConvertor_30_40.convertValueSet(theValueSet); } catch (FHIRException e) { throw new InternalErrorException(e); } - myValueSetResourceDao.update(valueSetDstu3, matchUrl, theRequestDetails); + + if (isBlank(valueSetDstu3.getIdElement().getIdPart())) { + String matchUrl = "ValueSet?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); + myValueSetResourceDao.update(valueSetDstu3, matchUrl); + } else { + myValueSetResourceDao.update(valueSetDstu3); + } } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java index 5800b10fa76..c8602b643da 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java @@ -4,7 +4,6 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.UrlUtil; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -28,6 +27,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; /* @@ -95,21 +95,33 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements } @Override - protected IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource, RequestDetails theRequestDetails) { - String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); - return myCodeSystemResourceDao.update(theCodeSystemResource, matchUrl, theRequestDetails).getId(); + protected IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource) { + if (isBlank(theCodeSystemResource.getIdElement().getIdPart())) { + String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); + return myCodeSystemResourceDao.update(theCodeSystemResource, matchUrl).getId(); + } else { + return myCodeSystemResourceDao.update(theCodeSystemResource).getId(); + } } @Override - protected void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap, RequestDetails theRequestDetails) { - String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); - myConceptMapResourceDao.update(theConceptMap, matchUrl, theRequestDetails); + protected void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap) { + if (isBlank(theConceptMap.getIdElement().getIdPart())) { + String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); + myConceptMapResourceDao.update(theConceptMap, matchUrl); + } else { + myConceptMapResourceDao.update(theConceptMap); + } } @Override - protected void createOrUpdateValueSet(ValueSet theValueSet, RequestDetails theRequestDetails) { - String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); - myValueSetResourceDao.update(theValueSet, matchUrl, theRequestDetails); + protected void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet) { + if (isBlank(theValueSet.getIdElement().getIdPart())) { + String matchUrl = "ValueSet?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); + myValueSetResourceDao.update(theValueSet, matchUrl); + } else { + myValueSetResourceDao.update(theValueSet); + } } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseHandler.java index c60070a7965..31af0aeff9b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseHandler.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.term.loinc; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.term.IRecordHandler; import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.ContactPoint; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.ValueSet; @@ -85,7 +86,7 @@ abstract class BaseHandler implements IRecordHandler { } - void addConceptMapEntry(ConceptMapping theMapping) { + void addConceptMapEntry(ConceptMapping theMapping, String theCopyright) { if (isBlank(theMapping.getSourceCode())) { return; } @@ -99,6 +100,13 @@ abstract class BaseHandler implements IRecordHandler { conceptMap.setId(theMapping.getConceptMapId()); conceptMap.setUrl(theMapping.getConceptMapUri()); conceptMap.setName(theMapping.getConceptMapName()); + conceptMap.setPublisher("Regentrief Institute, Inc."); + conceptMap.addContact() + .setName("Regentrief Institute, Inc.") + .addTelecom() + .setSystem(ContactPoint.ContactPointSystem.URL) + .setValue("https://loinc.org"); + conceptMap.setCopyright(theCopyright); myIdToConceptMaps.put(theMapping.getConceptMapId(), conceptMap); myConceptMaps.add(conceptMap); } else { @@ -164,6 +172,13 @@ abstract class BaseHandler implements IRecordHandler { vs.setId(theValueSetId); vs.setName(theValueSetName); vs.setStatus(Enumerations.PublicationStatus.ACTIVE); + vs.setPublisher("Regenstrief Institute, Inc."); + vs.addContact() + .setName("Regenstrief Institute, Inc.") + .addTelecom() + .setSystem(ContactPoint.ContactPointSystem.URL) + .setValue("https://loinc.org"); + vs.setCopyright("This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/"); myIdToValueSet.put(theValueSetId, vs); myValueSets.add(vs); } else { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java index 532ad314df2..fa3d23b1341 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java @@ -35,8 +35,8 @@ import static org.apache.commons.lang3.StringUtils.trim; public class LoincDocumentOntologyHandler extends BaseHandler implements IRecordHandler { - public static final String DOCUMENT_ONTOLOGY_CODES_VS_ID = "DOCUMENT-ONTOLOGY-CODES-VS"; - public static final String DOCUMENT_ONTOLOGY_CODES_VS_URI = "http://loinc.org/document-ontology-codes"; + public static final String DOCUMENT_ONTOLOGY_CODES_VS_ID = "loinc-document-ontology"; + public static final String DOCUMENT_ONTOLOGY_CODES_VS_URI = "http://loinc.org/vs/loinc-document-ontology"; public static final String DOCUMENT_ONTOLOGY_CODES_VS_NAME = "LOINC Document Ontology Codes"; private final Map myCode2Concept; private final TermCodeSystemVersion myCodeSystemVersion; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java index 7de16cf68ef..bbdf5583efa 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java @@ -57,6 +57,13 @@ public class LoincHandler implements IRecordHandler { TermConcept concept = new TermConcept(myCodeSystemVersion, code); concept.setDisplay(display); + if (!display.equalsIgnoreCase(shortName)) { + concept + .addDesignation() + .setUseDisplay("ShortName") + .setValue(shortName); + } + for (String nextPropertyName : myPropertyNames) { if (!theRecord.toMap().containsKey(nextPropertyName)) { continue; @@ -67,7 +74,7 @@ public class LoincHandler implements IRecordHandler { } } - Validate.isTrue(!myCode2Concept.containsKey(code)); + Validate.isTrue(!myCode2Concept.containsKey(code), "The code %s has appeared more than once", code); myCode2Concept.put(code, concept); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincIeeeMedicalDeviceCodeHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincIeeeMedicalDeviceCodeHandler.java index b836330ddcb..db42e7eff6b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincIeeeMedicalDeviceCodeHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincIeeeMedicalDeviceCodeHandler.java @@ -38,6 +38,7 @@ public class LoincIeeeMedicalDeviceCodeHandler extends BaseHandler implements IR public static final String LOINC_IEEE_CM_ID = "LOINC-IEEE-MEDICAL-DEVICE-CM"; public static final String LOINC_IEEE_CM_URI = "http://loinc.org/fhir/loinc-ieee-device-code-mappings"; public static final String LOINC_IEEE_CM_NAME = "LOINC/IEEE Device Code Mappings"; + private static final String CM_COPYRIGHT = "This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/. The LOINC/IEEE Medical Device Code Mapping Table contains content from IEEE (http://ieee.org), copyright © 2017 IEEE."; /** * Constructor @@ -68,7 +69,8 @@ public class LoincIeeeMedicalDeviceCodeHandler extends BaseHandler implements IR .setTargetCodeSystem(targetCodeSystemUri) .setTargetCode(ieeeCode) .setTargetDisplay(ieeeDisplayName) - .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL)); + .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL), + CM_COPYRIGHT); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartHandler.java index 3b0a990edd8..0b1600aa143 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartHandler.java @@ -29,6 +29,7 @@ import org.hl7.fhir.r4.model.ValueSet; import java.util.HashMap; import java.util.Map; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.trim; public class LoincPartHandler implements IRecordHandler { @@ -50,15 +51,23 @@ public class LoincPartHandler implements IRecordHandler { String partTypeName = trim(theRecord.get("PartTypeName")); String partName = trim(theRecord.get("PartName")); String partDisplayName = trim(theRecord.get("PartDisplayName")); - String status = trim(theRecord.get("Status")); - if (!"ACTIVE".equals(status)) { - return; - } + // Per Dan's note, we include deprecated parts +// String status = trim(theRecord.get("Status")); +// if (!"ACTIVE".equals(status)) { +// return; +// } TermConcept concept = new TermConcept(myCodeSystemVersion, partNumber); concept.setDisplay(partName); + if (isNotBlank(partDisplayName)) { + concept.addDesignation() + .setConcept(concept) + .setUseDisplay("PartDisplayName") + .setValue(partDisplayName); + } + myCode2Concept.put(partDisplayName, concept); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartLinkHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartLinkHandler.java index fd9be381ef4..4b6b63bf594 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartLinkHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartLinkHandler.java @@ -24,19 +24,19 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.term.IRecordHandler; import org.apache.commons.csv.CSVRecord; -import org.hl7.fhir.r4.model.ValueSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; import java.util.Map; import static org.apache.commons.lang3.StringUtils.trim; public class LoincPartLinkHandler implements IRecordHandler { + private static final Logger ourLog = LoggerFactory.getLogger(LoincPartLinkHandler.class); private final Map myCode2Concept; private final TermCodeSystemVersion myCodeSystemVersion; + private Long myPartCount; public LoincPartLinkHandler(TermCodeSystemVersion theCodeSystemVersion, Map theCode2concept) { myCodeSystemVersion = theCodeSystemVersion; @@ -53,17 +53,23 @@ public class LoincPartLinkHandler implements IRecordHandler { TermConcept loincConcept = myCode2Concept.get(loincNumber); TermConcept partConcept = myCode2Concept.get(partNumber); - if (loincConcept==null) { + if (loincConcept == null) { ourLog.warn("No loinc code: {}", loincNumber); return; } - if (partConcept==null) { - ourLog.warn("No part code: {}", partNumber); + if (partConcept == null) { + if (myPartCount == null) { + myPartCount = myCode2Concept + .keySet() + .stream() + .filter(t->t.startsWith("LP")) + .count(); + } + ourLog.debug("No part code: {} - Have {} part codes", partNumber, myPartCount); return; } // For now we're ignoring these } -private static final Logger ourLog = LoggerFactory.getLogger(LoincPartLinkHandler.class); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java index 4ada023ccd2..2988328a4c6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java @@ -33,13 +33,15 @@ import org.hl7.fhir.r4.model.ValueSet; import java.util.List; import java.util.Map; +import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.trim; public class LoincPartRelatedCodeMappingHandler extends BaseHandler implements IRecordHandler { public static final String LOINC_PART_MAP_ID = "LOINC-PART-MAP"; - public static final String LOINC_PART_MAP_URI = "http://loinc.org/fhir/loinc-part-map"; + public static final String LOINC_PART_MAP_URI = "http://loinc.org/cm/loinc-parts-to-snomed-ct"; public static final String LOINC_PART_MAP_NAME = "LOINC Part Map"; + private static final String CM_COPYRIGHT = "This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/. The LOINC Part File, LOINC/SNOMED CT Expression Association and Map Sets File, RELMA database and associated search index files include SNOMED Clinical Terms (SNOMED CT®) which is used by permission of the International Health Terminology Standards Development Organisation (IHTSDO) under license. All rights are reserved. SNOMED CT® was originally created by The College of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO. Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. Under the terms of the Affiliate License, use of SNOMED CT in countries that are not IHTSDO Members is subject to reporting and fee payment obligations. However, IHTSDO agrees to waive the requirements to report and pay fees for use of SNOMED CT content included in the LOINC Part Mapping and LOINC Term Associations for purposes that support or enable more effective use of LOINC. This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov."; private final Map myCode2Concept; private final TermCodeSystemVersion myCodeSystemVersion; private final List myConceptMaps; @@ -68,7 +70,8 @@ public class LoincPartRelatedCodeMappingHandler extends BaseHandler implements I String extCodeSystemCopyrightNotice = trim(theRecord.get("ExtCodeSystemCopyrightNotice")); Enumerations.ConceptMapEquivalence equivalence; - switch (mapType) { + switch (trim(defaultString(mapType))) { + case "": case "Exact": // 'equal' is more exact than 'equivalent' in the equivalence codes equivalence = Enumerations.ConceptMapEquivalence.EQUAL; @@ -80,7 +83,7 @@ public class LoincPartRelatedCodeMappingHandler extends BaseHandler implements I equivalence = Enumerations.ConceptMapEquivalence.WIDER; break; default: - throw new InternalErrorException("Unknown MapType: " + mapType); + throw new InternalErrorException("Unknown MapType '" + mapType + "' for PartNumber: " + partNumber); } addConceptMapEntry( @@ -96,7 +99,9 @@ public class LoincPartRelatedCodeMappingHandler extends BaseHandler implements I .setTargetDisplay(extCodeDisplayName) .setTargetCodeSystemVersion(extCodeSystemVersion) .setEquivalence(equivalence) - .setCopyright(extCodeSystemCopyrightNotice)); + .setCopyright(extCodeSystemCopyrightNotice), + CM_COPYRIGHT + ); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java index fbe9b28ff7f..a9555fd78b5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java @@ -37,17 +37,27 @@ import static org.apache.commons.lang3.StringUtils.trim; public class LoincRsnaPlaybookHandler extends BaseHandler implements IRecordHandler { - public static final String RSNA_CODES_VS_ID = "RSNA-LOINC-CODES-VS"; - public static final String RSNA_CODES_VS_URI = "http://loinc.org/rsna-codes"; - public static final String RSNA_CODES_VS_NAME = "RSNA Playbook"; + public static final String RSNA_CODES_VS_ID = "loinc-rsna-radiology-playbook"; + public static final String RSNA_CODES_VS_URI = "http://loinc.org/vs/loinc-rsna-radiology-playbook"; + public static final String RSNA_CODES_VS_NAME = "LOINC/RSNA Radiology Playbook"; public static final String RID_MAPPING_CM_ID = "LOINC-TO-RID-CODES-CM"; public static final String RID_MAPPING_CM_URI = "http://loinc.org/rid-codes"; public static final String RID_MAPPING_CM_NAME = "RSNA Playbook RID Codes Mapping"; - public static final String RID_CS_URI = "http://rid"; + public static final String RID_CS_URI = "http://www.radlex.org"; public static final String RPID_MAPPING_CM_ID = "LOINC-TO-RPID-CODES-CM"; public static final String RPID_MAPPING_CM_URI = "http://loinc.org/rpid-codes"; public static final String RPID_MAPPING_CM_NAME = "RSNA Playbook RPID Codes Mapping"; - public static final String RPID_CS_URI = "http://rpid"; + /* + * About these being the same - Per Dan: + * We had some discussion about this, and both + * RIDs (RadLex clinical terms) and RPIDs (Radlex Playbook Ids) + * belong to the same "code system" since they will never collide. + * The codesystem uri is "http://www.radlex.org". FYI, that's + * now listed on the FHIR page: + * https://www.hl7.org/fhir/terminologies-systems.html + */ + public static final String RPID_CS_URI = RID_CS_URI; + private static final String CM_COPYRIGHT = "This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/. The LOINC/RSNA Radiology Playbook and the LOINC Part File contain content from RadLex® (http://rsna.org/RadLex.aspx), copyright © 2005-2017, The Radiological Society of North America, Inc., available at no cost under the license at http://www.rsna.org/uploadedFiles/RSNA/Content/Informatics/RadLex_License_Agreement_and_Terms_of_Use_V2_Final.pdf."; private final Map myCode2Concept; private final TermCodeSystemVersion myCodeSystemVersion; private final Set myPropertyNames; @@ -116,6 +126,51 @@ public class LoincRsnaPlaybookHandler extends BaseHandler implements IRecordHand case "Rad.Modality.Modality type": loincCodePropName = "rad-modality-modality-type"; break; + case "Rad.Modality.Modality subtype": + loincCodePropName = "rad-modality-modality-subtype"; + break; + case "Rad.Anatomic Location.Laterality": + loincCodePropName = "rad-anatomic-location-laterality"; + break; + case "Rad.Anatomic Location.Laterality.Presence": + loincCodePropName = "rad-anatomic-location-laterality-presence"; + break; + case "Rad.Guidance for.Action": + loincCodePropName = "rad-guidance-for-action"; + break; + case "Rad.Guidance for.Approach": + loincCodePropName = "rad-guidance-for-approach"; + break; + case "Rad.Guidance for.Object": + loincCodePropName = "rad-guidance-for-object"; + break; + case "Rad.Guidance for.Presence": + loincCodePropName = "rad-guidance-for-presence"; + break; + case "Rad.Maneuver.Maneuver type": + loincCodePropName = "rad-maneuver-maneuver-type"; + break; + case "Rad.Pharmaceutical.Route": + loincCodePropName = "rad-pharmaceutical-route"; + break; + case "Rad.Pharmaceutical.Substance Given": + loincCodePropName = "rad-pharmaceutical-substance-given"; + break; + case "Rad.Reason for Exam": + loincCodePropName = "rad-reason-for-exam"; + break; + case "Rad.Subject": + loincCodePropName = "rad-subject"; + break; + case "Rad.Timing": + loincCodePropName = "rad-timing"; + break; + case "Rad.View.Aggregation": + loincCodePropName = "rad-view-view-aggregation"; + break; + case "Rad.View.View type": + loincCodePropName = "rad-view-view-type"; + break; default: throw new InternalErrorException("Unknown PartTypeName: " + partTypeName); } @@ -138,7 +193,8 @@ public class LoincRsnaPlaybookHandler extends BaseHandler implements IRecordHand .setTargetCodeSystem(RID_CS_URI) .setTargetCode(rid) .setTargetDisplay(preferredName) - .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL)); + .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL), + CM_COPYRIGHT); } // LOINC Term -> Radlex RPID code mappings @@ -154,7 +210,8 @@ public class LoincRsnaPlaybookHandler extends BaseHandler implements IRecordHand .setTargetCodeSystem(RPID_CS_URI) .setTargetCode(rpid) .setTargetDisplay(longName) - .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL)); + .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL), + CM_COPYRIGHT); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincTop2000LabResultsSiHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincTop2000LabResultsSiHandler.java index 33678c1c402..91c7e959491 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincTop2000LabResultsSiHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincTop2000LabResultsSiHandler.java @@ -29,8 +29,8 @@ import java.util.Map; public class LoincTop2000LabResultsSiHandler extends BaseLoincTop2000LabResultsHandler { - public static final String TOP_2000_SI_VS_ID = "TOP-2000-LABRESULTS-SI"; - public static final String TOP_2000_SI_VS_URI = "http://loinc.org/top-2000-lab-results-si"; + public static final String TOP_2000_SI_VS_ID = "top-2000-lab-observations-si"; + public static final String TOP_2000_SI_VS_URI = "http://loinc.org/vs/top-2000-lab-observations-si"; public static final String TOP_2000_SI_VS_NAME = "Top 2000 Lab Results SI"; public LoincTop2000LabResultsSiHandler(Map theCode2concept, List theValueSets, List theConceptMaps) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincTop2000LabResultsUsHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincTop2000LabResultsUsHandler.java index d5f65073d1a..64f11b92303 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincTop2000LabResultsUsHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincTop2000LabResultsUsHandler.java @@ -29,8 +29,8 @@ import java.util.Map; public class LoincTop2000LabResultsUsHandler extends BaseLoincTop2000LabResultsHandler { - public static final String TOP_2000_US_VS_ID = "TOP-2000-LABRESULTS-US"; - public static final String TOP_2000_US_VS_URI = "http://loinc.org/top-2000-lab-results-us"; + public static final String TOP_2000_US_VS_ID = "top-2000-lab-observations-us"; + public static final String TOP_2000_US_VS_URI = "http://loinc.org/vs/top-2000-lab-observations-us"; public static final String TOP_2000_US_VS_NAME = "Top 2000 Lab Results US"; public LoincTop2000LabResultsUsHandler(Map theCode2concept, List theValueSets, List theConceptMaps) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java index 39a3a2cea09..a9b7206c9ce 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java @@ -40,4 +40,33 @@ public class JpaConstants { */ public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template"; + + /** + * This extension URL indicates whether a REST HOOK delivery should + * include the version ID when delivering. + *

+ * This extension should be of type boolean and should be + * placed on the Subscription.channel element. + *

+ */ + public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids"; + + /** + * This extension URL indicates whether a REST HOOK delivery should + * reload the resource and deliver the latest version always. This + * could be useful for example if a resource which triggers a + * subscription gets updated many times in short succession and there + * is no value in delivering the older versions. + *

+ * Note that if the resource is now deleted, this may cause + * the delivery to be cancelled altogether. + *

+ * + *

+ * This extension should be of type boolean and should be + * placed on the Subscription.channel element. + *

+ */ + public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version"; + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index 4d886041433..1998380c96f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder; +import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import org.apache.commons.dbcp2.BasicDataSource; import org.hibernate.jpa.HibernatePersistenceProvider; @@ -104,7 +105,7 @@ public class TestR4Config extends BaseJavaConfigR4 { DataSource dataSource = ProxyDataSourceBuilder .create(retVal) -// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") + .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) .countQuery(new ThreadQueryCountHolder()) .build(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 1b6e270875e..6639e4bf878 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -281,6 +281,7 @@ public abstract class BaseJpaTest { @Override public Void doInTransaction(TransactionStatus theStatus) { entityManager.createQuery("DELETE from " + TermConceptProperty.class.getSimpleName() + " d").executeUpdate(); + entityManager.createQuery("DELETE from " + TermConceptDesignation.class.getSimpleName() + " d").executeUpdate(); entityManager.createQuery("DELETE from " + TermConcept.class.getSimpleName() + " d").executeUpdate(); for (TermCodeSystem next : entityManager.createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class).getResultList()) { next.setCurrentVersion(null); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UniqueSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UniqueSearchParamTest.java index 375771e53fc..be99f609aa3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UniqueSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UniqueSearchParamTest.java @@ -18,6 +18,7 @@ import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; +import org.springframework.test.context.TestPropertySource; import java.util.Collections; import java.util.List; @@ -27,6 +28,11 @@ import static org.hamcrest.Matchers.empty; import static org.junit.Assert.*; @SuppressWarnings({"unchecked", "deprecation"}) +@TestPropertySource(properties = { + // Since scheduled tasks can cause searches, which messes up the + // value returned by SearchBuilder.getLastHandlerMechanismForUnitTest() + "scheduling_disabled=true" +}) public class FhirResourceDaoDstu3UniqueSearchParamTest extends BaseJpaDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3UniqueSearchParamTest.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemTest.java index e0b580fd79a..c535f1fdfb4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemTest.java @@ -1,31 +1,19 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -import java.io.IOException; - -import org.hl7.fhir.dstu3.model.BooleanType; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.CodeType; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.Parameters; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.dstu3.model.UriType; -import org.hl7.fhir.dstu3.model.ValueSet; +import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.util.TestUtil; +import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.springframework.transaction.annotation.Transactional; -import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import ca.uhn.fhir.util.TestUtil; +import java.io.IOException; + +import static org.junit.Assert.*; public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDstu3Test { @@ -58,34 +46,34 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst ourLog.info(resp); assertEquals("name", respParam.getParameter().get(0).getName()); - assertEquals(("SYSTEM NAME"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals(("SYSTEM NAME"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals("display", respParam.getParameter().get(1).getName()); - assertEquals("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("Parent A", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); assertEquals("abstract", respParam.getParameter().get(2).getName()); - assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).getValue().booleanValue()); // With HTTP GET respParam = ourClient - .operation() - .onType(CodeSystem.class) - .named("lookup") - .withParameter(Parameters.class, "code", new CodeType("ParentA")) - .andParameter("system", new UriType(FhirResourceDaoDstu3TerminologyTest.URL_MY_CODE_SYSTEM)) - .useHttpGet() - .execute(); + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ParentA")) + .andParameter("system", new UriType(FhirResourceDaoDstu3TerminologyTest.URL_MY_CODE_SYSTEM)) + .useHttpGet() + .execute(); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); assertEquals("name", respParam.getParameter().get(0).getName()); - assertEquals(("SYSTEM NAME"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals(("SYSTEM NAME"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals("display", respParam.getParameter().get(1).getName()); - assertEquals("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("Parent A", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); assertEquals("abstract", respParam.getParameter().get(2).getName()); - assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).getValue().booleanValue()); } - + @Test public void testLookupOperationByCodeAndSystemBuiltInCode() { Parameters respParam = ourClient @@ -100,13 +88,13 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst ourLog.info(resp); assertEquals("name", respParam.getParameter().get(0).getName()); - assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals(("Unknown"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals("display", respParam.getParameter().get(1).getName()); - assertEquals("Accession ID", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("Accession ID", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); assertEquals("abstract", respParam.getParameter().get(2).getName()); - assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).getValue().booleanValue()); } - + @Test public void testLookupOperationByCodeAndSystemBuiltInNonexistantCode() { //@formatter:off @@ -141,11 +129,11 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst ourLog.info(resp); assertEquals("name", respParam.getParameter().get(0).getName()); - assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals(("Unknown"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals("display", respParam.getParameter().get(1).getName()); - assertEquals(("Systolic blood pressure--expiration"), ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals(("Systolic blood pressure--expiration"), ((StringType) respParam.getParameter().get(1).getValue()).getValue()); assertEquals("abstract", respParam.getParameter().get(2).getName()); - assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).getValue().booleanValue()); } @Test @@ -181,11 +169,11 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst ourLog.info(resp); assertEquals("name", respParam.getParameter().get(0).getName()); - assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals(("Unknown"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals("display", respParam.getParameter().get(1).getName()); - assertEquals(("Systolic blood pressure--expiration"), ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals(("Systolic blood pressure--expiration"), ((StringType) respParam.getParameter().get(1).getValue()).getValue()); assertEquals("abstract", respParam.getParameter().get(2).getName()); - assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).getValue().booleanValue()); } @Test @@ -259,17 +247,17 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst ourLog.info(resp); assertEquals("name", respParam.getParameter().get(0).getName()); - assertEquals("Unknown", ((StringType)respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("Unknown", ((StringType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals("display", respParam.getParameter().get(1).getName()); - assertEquals("Married", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("Married", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); assertEquals("abstract", respParam.getParameter().get(2).getName()); - assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).booleanValue()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).booleanValue()); } - + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); } - + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3Test.java index abd83f128cf..60b94ec3d2c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3Test.java @@ -134,7 +134,7 @@ public class TerminologyUploaderProviderDstu3Test extends BaseResourceProviderDs @Test public void testUploadMissingUrl() throws Exception { byte[] packageBytes = createSctZip(); - + try { ourClient .operation() @@ -207,7 +207,7 @@ public class TerminologyUploaderProviderDstu3Test extends BaseResourceProviderDs assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1)); } - + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index b6557c4243c..f5a032617c6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -182,6 +182,15 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { return ids; } + @Test + @Ignore + public void test() throws IOException { + HttpGet get = new HttpGet(ourServerBase + "/QuestionnaireResponse?_count=50&status=completed&questionnaire=ARIncenterAbsRecord&_lastUpdated=%3E"+UrlUtil.escapeUrlParam("=2018-01-01")+"&context.organization=O3435"); + ourLog.info("*** MAKING QUERY"); + ourHttpClient.execute(get); + System.exit(0); + } + @Test public void testBundleCreate() throws Exception { IGenericClient client = myClient; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java index 5a9c817e71e..4de1ab758ac 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java @@ -2,8 +2,10 @@ package ca.uhn.fhir.jpa.subscription.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; +import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; @@ -172,7 +174,38 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test { } + @Test + public void testRestHookSubscriptionApplicationJsonDisableVersionIdInDelivery() throws Exception { + String payload = "application/json"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + + Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase); + subscription1 + .getChannel() + .addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true")); + subscription1 + .getChannel() + .addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true")); + myClient.update().resource(subscription1).execute(); + waitForQueueToDrain(); + + Observation observation1 = sendObservation(code, "SNOMED-CT"); + + // Should see 1 subscription notification + waitForQueueToDrain(); + waitForSize(0, ourCreatedObservations); + waitForSize(1, ourUpdatedObservations); + assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); + + assertEquals(observation1.getIdElement().getIdPart(), ourUpdatedObservations.get(0).getIdElement().getIdPart()); + assertEquals(null, ourUpdatedObservations.get(0).getIdElement().getVersionIdPart()); + } + + + @Test public void testRestHookSubscriptionApplicationJson() throws Exception { String payload = "application/json"; @@ -191,6 +224,8 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test { waitForSize(1, ourUpdatedObservations); assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); + assertEquals("1", ourUpdatedObservations.get(0).getIdElement().getVersionIdPart()); + Subscription subscriptionTemp = myClient.read(Subscription.class, subscription2.getId()); Assert.assertNotNull(subscriptionTemp); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java index 9e795d95be6..0b0214562b2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.term; +import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.entity.ResourceTable; @@ -13,6 +14,7 @@ import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.ValueSet; +import org.junit.After; import org.junit.AfterClass; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -42,6 +44,14 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { TermCodeSystemVersion cs = new TermCodeSystemVersion(); cs.setResource(table); + TermConcept parent; + parent = new TermConcept(cs, "ParentWithNoChildrenA"); + cs.getConcepts().add(parent); + parent = new TermConcept(cs, "ParentWithNoChildrenB"); + cs.getConcepts().add(parent); + parent = new TermConcept(cs, "ParentWithNoChildrenC"); + cs.getConcepts().add(parent); + TermConcept parentA = new TermConcept(cs, "ParentA"); cs.getConcepts().add(parentA); @@ -56,6 +66,11 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { TermConcept childAAB = new TermConcept(cs, "childAAB"); childAAB.addPropertyString("propA", "valueAAB"); childAAB.addPropertyString("propB", "foo"); + childAAB.addDesignation() + .setUseSystem("D1S") + .setUseCode("D1C") + .setUseDisplay("D1D") + .setValue("D1V"); childAA.addChild(childAAB, RelationshipTypeEnum.ISA); TermConcept childAB = new TermConcept(cs, "childAB"); @@ -190,9 +205,75 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { ValueSet outcome = myTermSvc.expandValueSet(vs); codes = toCodesContains(outcome.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("ParentA", "childAAA", "childAAB", "childAA", "childAB", "ParentB")); + assertThat(codes, containsInAnyOrder("ParentWithNoChildrenA", "ParentWithNoChildrenB", "ParentWithNoChildrenC", "ParentA", "childAAA", "childAAB", "childAA", "childAB", "ParentB")); } + @Test + public void testPropertiesAndDesignationsPreservedInExpansion() { + createCodeSystem(); + + List codes; + + ValueSet vs = new ValueSet(); + ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(CS_URL); + include.addConcept().setCode("childAAB"); + ValueSet outcome = myTermSvc.expandValueSet(vs); + + codes = toCodesContains(outcome.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("childAAB")); + + ValueSet.ValueSetExpansionContainsComponent concept = outcome.getExpansion().getContains().get(0); + assertEquals("childAAB", concept.getCode()); + assertEquals("http://example.com/my_code_system", concept.getSystem()); + assertEquals(null, concept.getDisplay()); + assertEquals("D1S", concept.getDesignation().get(0).getUse().getSystem()); + assertEquals("D1C", concept.getDesignation().get(0).getUse().getCode()); + assertEquals("D1D", concept.getDesignation().get(0).getUse().getDisplay()); + assertEquals("D1V", concept.getDesignation().get(0).getValue()); + } + + @After + public void after() { + myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + } + + + @Test + public void testCreatePropertiesAndDesignationsWithDeferredConcepts() { + myDaoConfig.setDeferIndexingForCodesystemsOfSize(1); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + + createCodeSystem(); + + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + + ValueSet vs = new ValueSet(); + ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(CS_URL); + include.addConcept().setCode("childAAB"); + ValueSet outcome = myTermSvc.expandValueSet(vs); + + List codes = toCodesContains(outcome.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("childAAB")); + + ValueSet.ValueSetExpansionContainsComponent concept = outcome.getExpansion().getContains().get(0); + assertEquals("childAAB", concept.getCode()); + assertEquals("http://example.com/my_code_system", concept.getSystem()); + assertEquals(null, concept.getDisplay()); + assertEquals("D1S", concept.getDesignation().get(0).getUse().getSystem()); + assertEquals("D1C", concept.getDesignation().get(0).getUse().getCode()); + assertEquals("D1D", concept.getDesignation().get(0).getUse().getDisplay()); + assertEquals("D1V", concept.getDesignation().get(0).getValue()); + } + + @Test public void testFindCodesAbove() { IIdType id = createCodeSystem(); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt b/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt index a8fe4c2edd0..a09a63c8e1a 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt +++ b/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt @@ -108,7 +108,6 @@ where res_id in ( drop table hfj_history_tag cascade constraints; drop table hfj_forced_id cascade constraints; -drop table HFJ_SUBSCRIPTION_STATS cascade constraints; drop table hfj_res_link cascade constraints; drop table hfj_spidx_coords cascade constraints; drop table hfj_spidx_date cascade constraints; diff --git a/pom.xml b/pom.xml index 6064b0c4ba5..4e90c4737d8 100644 --- a/pom.xml +++ b/pom.xml @@ -451,7 +451,7 @@ 9.4.8.v20171121 3.0.2 - 5.2.12.Final + 5.2.16.Final 5.4.1.Final 5.7.1.Final @@ -998,7 +998,7 @@ org.javassist javassist - 3.20.0-GA + 3.22.0-GA org.mockito diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 1e5d2924c82..387de638fb3 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,6 +6,46 @@ HAPI FHIR Changelog + + + The version of a few dependencies have been bumped to the + latest versions (dependent HAPI modules listed in brackets): + +
  • Hibernate (JPA): 5.2.12.Final -> 5.2.16.Final
  • +
  • Javassist (JPA): 3.20.0-GA -> 3.22.0-GA
  • + + ]]> +
    + + When performing a FHIR resource update in the JPA server + where the update happens within a transaction, and the + resource being updated contains placeholder IDs, and + the resource has not actually changed, a new version was + created even though there was not actually any change. + This particular combination of circumstances seems very + specific and improbable, but it is quite common for some + types of solutions (e.g. mapping HL7v2 data) so this + fix can prevent significant wasted space in some cases. + + + JPA server index tables did not have a column length specified + on the resource type column. This caused the default of 255 to + be used, which wasted a lot of space since resource names are all + less than 30 chars long and a single resource can have 10-100+ + index rows depending on configuration. This has now been set + to a much more sensible 30. + + + The LOINC uploader for the JPA Terminology Server has been + significantly beefed up so that it now takes in the full + set of LOINC distribution artifacts, and creates not only + the LOINC CodeSystem but a complete set of concept properties, + a number of LOINC ValueSets, and a number of LOINC ConceptMaps. + This work was sponsored by the Regenstrief Institute. Thanks + to Regenstrief for their support! + +
    This release corrects an inefficiency in the JPA Server, but requires a schema