diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index af51741bc5d..f6c6a2e2bbc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -588,7 +588,10 @@ class ModelScanner { */ List> refTypesList = new ArrayList>(); for (Class nextType : childAnnotation.type()) { - if (IBaseResource.class.isAssignableFrom(nextType) == false) { + if (IBaseReference.class.isAssignableFrom(nextType)) { + refTypesList.add(myVersion.isRi() ? IAnyResource.class : IResource.class); + continue; + } else if (IBaseResource.class.isAssignableFrom(nextType) == false) { throw new ConfigurationException("Field '" + next.getName() + "' in class '" + next.getDeclaringClass().getCanonicalName() + "' is of type " + BaseResourceReferenceDt.class + " but contains a non-resource type: " + nextType.getCanonicalName()); } refTypesList.add((Class) nextType); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java new file mode 100644 index 00000000000..177a2c00795 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java @@ -0,0 +1,21 @@ +package ca.uhn.fhir.util; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class ValidateUtil { + + public static void isNotNullOrThrowInvalidRequest(boolean theSuccess, String theMessage) { + if (theSuccess == false) { + throw new InvalidRequestException(theMessage); + } + } + + public static void isNotBlankOrThrowInvalidRequest(String theString, String theMessage) { + if (isBlank(theString)) { + throw new InvalidRequestException(theMessage); + } + } + +} diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index c8910c7f7b2..fcde6e42738 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -65,4 +65,6 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulCreate=Successfully create ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1} -ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1} \ No newline at end of file +ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1} + +ca.uhn.fhir.jpa.term.TerminologySvcImpl.cannotCreateDuplicateCodeSystemUri=Can not create multiple code systems with URI "{0}", already have one with resource ID: {1} \ No newline at end of file diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ValidateUtilTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ValidateUtilTest.java new file mode 100644 index 00000000000..89e9c415253 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ValidateUtilTest.java @@ -0,0 +1,50 @@ +package ca.uhn.fhir.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class ValidateUtilTest { + + @Test + public void testValidate() { + ValidateUtil.isNotNullOrThrowInvalidRequest(true, ""); + + try { + ValidateUtil.isNotNullOrThrowInvalidRequest(false, "The message"); + fail(); + } catch (InvalidRequestException e) { + assertEquals("The message", e.getMessage()); + } + } + + @Test + public void testIsNotBlank() { + ValidateUtil.isNotBlankOrThrowInvalidRequest("aa", ""); + + try { + ValidateUtil.isNotBlankOrThrowInvalidRequest("", "The message"); + fail(); + } catch (InvalidRequestException e) { + assertEquals("The message", e.getMessage()); + } + + try { + ValidateUtil.isNotBlankOrThrowInvalidRequest(null, "The message"); + fail(); + } catch (InvalidRequestException e) { + assertEquals("The message", e.getMessage()); + } + + try { + ValidateUtil.isNotBlankOrThrowInvalidRequest(" ", "The message"); + fail(); + } catch (InvalidRequestException e) { + assertEquals("The message", e.getMessage()); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index ded23af9b26..d9c22c8bbd4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.config; import javax.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -37,6 +38,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.term.ITerminologySvc; +import ca.uhn.fhir.jpa.term.TerminologySvcImpl; @Configuration @EnableScheduling @@ -95,6 +98,11 @@ public class BaseConfig implements SchedulingConfigurer { return ourFhirContextDstu3; } + @Bean(autowire=Autowire.BY_TYPE) + public ITerminologySvc terminologyService() { + return new TerminologySvcImpl(); + } + @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler(); 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 41e9614d380..b67c83c5643 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 @@ -571,9 +571,14 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { } } + + ourLog.info("Flushing context after {}", theActionName); + myEntityManager.flush(); long delay = System.currentTimeMillis() - start; - ourLog.info(theActionName + " completed in {}ms", new Object[] { delay }); + int numEntries = theRequest.getEntry().size(); + long delayPer = delay / numEntries; + ourLog.info("{} completed in {}ms ({} entries at {}ms per entry)", new Object[] { theActionName , delay, numEntries, delayPer }); response.setType(BundleTypeEnum.TRANSACTION_RESPONSE); return response; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java new file mode 100644 index 00000000000..059a9afd90c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2016 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 org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import ca.uhn.fhir.jpa.entity.TermCodeSystem; + +public interface ITermCodeSystemDao extends JpaRepository { + + @Query("SELECT cs FROM TermCodeSystem cs WHERE cs.myCodeSystemUri = :code_system_uri") + TermCodeSystem findByCodeSystemUri(@Param("code_system_uri") String theCodeSystemUri); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java new file mode 100644 index 00000000000..0c30371a4e2 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2016 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 org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; + +public interface ITermCodeSystemVersionDao extends JpaRepository { + + @Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myResource.myId = :resource_id AND cs.myResourceVersionId = :version_id") + TermCodeSystemVersion findByCodeSystemResourceAndVersion(@Param("resource_id") Long theCodeSystemResourcePid, @Param("version_id") Long theCodeSystemVersionPid); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java new file mode 100644 index 00000000000..41ec4c1f31a --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2016 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 org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; + +public interface ITermConceptDao extends JpaRepository { + + @Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system AND c.myCode = :code") + TermConcept findByCodeSystemAndCode(@Param("code_system") TermCodeSystemVersion theCodeSystem, @Param("code") String theCode); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java new file mode 100644 index 00000000000..6216eb19f4c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java @@ -0,0 +1,29 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2016 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 org.springframework.data.jpa.repository.JpaRepository; + +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; + +public interface ITermConceptParentChildLinkDao extends JpaRepository { + // nothing +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java index 135fd7635ec..f45f3edf551 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java @@ -25,6 +25,7 @@ import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; @@ -34,24 +35,39 @@ import javax.persistence.UniqueConstraint; //@formatter:off @Table(name="TRM_CODESYSTEM", uniqueConstraints= { - @UniqueConstraint(name="IDX_CS_RESOURCEPID", columnNames= {"RES_ID"}) + @UniqueConstraint(name="IDX_CS_CODESYSTEM", columnNames= {"CODE_SYSTEM_URI"}) }) @Entity() //@formatter:on public class TermCodeSystem implements Serializable { private static final long serialVersionUID = 1L; - @Id() - @SequenceGenerator(name="SEQ_CODESYSTEM_PID", sequenceName="SEQ_CODESYSTEM_PID") - @GeneratedValue() - @Column(name="PID") - private Long myPid; - - @OneToOne() - @JoinColumn(name="RES_ID", referencedColumnName="RES_ID", nullable=false, updatable=false) - private ResourceTable myResource; - - @Column(name="RES_VERSION_ID", nullable=false, updatable=false) - private Long myResourceVersionId; + @Column(name="CODE_SYSTEM_URI", nullable=false) + private String myCodeSystemUri; + @Id() + @SequenceGenerator(name = "SEQ_CODESYSTEM_PID", sequenceName = "SEQ_CODESYSTEM_PID") + @GeneratedValue(strategy=GenerationType.AUTO, generator="SEQ_CODESYSTEM_PID") + @Column(name = "PID") + private Long myPid; + + @OneToOne() + @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false) + private ResourceTable myResource; + + public String getCodeSystemUri() { + return myCodeSystemUri; + } + + public ResourceTable getResource() { + return myResource; + } + + public void setCodeSystemUri(String theCodeSystemUri) { + myCodeSystemUri = theCodeSystemUri; + } + + public void setResource(ResourceTable theResource) { + myResource = theResource; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java new file mode 100644 index 00000000000..4d1f35de323 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java @@ -0,0 +1,88 @@ +package ca.uhn.fhir.jpa.entity; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2016 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 java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +//@formatter:off +@Table(name="TRM_CODESYSTEM_VER", uniqueConstraints= { + @UniqueConstraint(name="IDX_CSV_RESOURCEPID_AND_VER", columnNames= {"RES_ID", "RES_VERSION_ID"}) +}) +@Entity() +//@formatter:on +public class TermCodeSystemVersion implements Serializable { + private static final long serialVersionUID = 1L; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem") + private Collection myConcepts; + + @Id() + @SequenceGenerator(name = "SEQ_CODESYSTEMVER_PID", sequenceName = "SEQ_CODESYSTEMVER_PID") + @GeneratedValue(strategy=GenerationType.AUTO, generator="SEQ_CODESYSTEMVER_PID") + @Column(name = "PID") + private Long myPid; + + @OneToOne() + @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false) + private ResourceTable myResource; + + @Column(name = "RES_VERSION_ID", nullable = false, updatable = false) + private Long myResourceVersionId; + + public Collection getConcepts() { + if (myConcepts == null) { + myConcepts = new ArrayList(); + } + return myConcepts; + } + + public ResourceTable getResource() { + return myResource; + } + + public Long getResourceVersionId() { + return myResourceVersionId; + } + + public void setResource(ResourceTable theResource) { + myResource = theResource; + } + + public void setResourceVersionId(Long theResourceVersionId) { + myResourceVersionId = theResourceVersionId; + } + +} 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 53be0e78bfd..753d7db7ae9 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 @@ -21,36 +21,138 @@ package ca.uhn.fhir.jpa.entity; */ import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.ForeignKey; import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import javax.persistence.SequenceGenerator; import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; @Entity -@Table(name="TRM_CONCEPT") +@Table(name="TRM_CONCEPT", uniqueConstraints= { + @UniqueConstraint(name="IDX_CONCEPT_CS_CODE", columnNames= {"CODESYSTEM_PID", "CODE"}) +}) public class TermConcept implements Serializable { private static final long serialVersionUID = 1L; - @Id() - @SequenceGenerator(name="SEQ_CONCEPT_PID", sequenceName="SEQ_CONCEPT_PID") - @GeneratedValue() - @Column(name="PID") - private Long myPid; + @OneToMany(fetch=FetchType.LAZY, mappedBy="myParent") + private Collection myChildren; - @ManyToOne() - @JoinColumn(name="CODESYSTEM_PID", referencedColumnName="PID", foreignKey=@ForeignKey(name="FK_CONCEPT_PID_CS_PID")) - private TermCodeSystem myCodeSystem; - @Column(name="CODE", length=100, nullable=false) private String myCode; + @ManyToOne() + @JoinColumn(name="CODESYSTEM_PID", referencedColumnName="PID", foreignKey=@ForeignKey(name="FK_CONCEPT_PID_CS_PID")) + private TermCodeSystemVersion myCodeSystem; + @Column(name="DISPLAY", length=200, nullable=true) private String myDisplay; + + @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="myChild") + private Collection myParents; + + @Id() + @SequenceGenerator(name="SEQ_CONCEPT_PID", sequenceName="SEQ_CONCEPT_PID") + @GeneratedValue(strategy=GenerationType.AUTO, generator="SEQ_CONCEPT_PID") + @Column(name="PID") + private Long myPid; + + public TermConcept() { + super(); + } + + public TermConcept(TermCodeSystemVersion theCs, String theCode) { + setCodeSystem(theCs); + setCode(theCode); + } + + public TermConcept addChild(TermConcept theChild) { + Validate.notNull(theChild.getCodeSystem(), "theChild.getCodeSystem() must not return null"); + TermConceptParentChildLink link = new TermConceptParentChildLink(); + link.setParent(this); + link.setCodeSystem(theChild.getCodeSystem()); + link.setChild(theChild); + getChildren().add(link); + return this; + } + + @Override + public boolean equals(Object theObj) { + if (!(theObj instanceof TermConcept)) { + return false; + } + if (theObj == this) { + return true; + } + + TermConcept obj = (TermConcept)theObj; + if (obj.myPid == null) { + return false; + } + + EqualsBuilder b = new EqualsBuilder(); + b.append(myPid, obj.myPid); + return b.isEquals(); + } + + public Collection getChildren() { + if (myChildren == null) { + myChildren = new ArrayList(); + } + return myChildren; + } + + public String getCode() { + return myCode; + } + + public TermCodeSystemVersion getCodeSystem() { + return myCodeSystem; + } + + public String getDisplay() { + return myDisplay; + } + + public Collection getParents() { + if (myParents == null) { + myParents = new ArrayList(); + } + return myParents; + } + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(myPid); + return b.toHashCode(); + } + + public void setCode(String theCode) { + myCode = theCode; + } + + public void setCodeSystem(TermCodeSystemVersion theCodeSystem) { + myCodeSystem = theCodeSystem; + } + + public void setDisplay(String theDisplay) { + myDisplay = theDisplay; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java index 66b877453f8..287af9bb4a1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java @@ -24,7 +24,9 @@ import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.ForeignKey; import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @@ -32,26 +34,50 @@ import javax.persistence.SequenceGenerator; import javax.persistence.Table; @Entity -@Table(name="TRM_CONCEPT") +@Table(name="TRM_CONCEPT_PC_LINK") public class TermConceptParentChildLink implements Serializable { private static final long serialVersionUID = 1L; - + + @ManyToOne() + @JoinColumn(name="CHILD_PID", nullable=false, referencedColumnName="PID", foreignKey=@ForeignKey(name="FK_TERM_CONCEPTPC_CHILD")) + private TermConcept myChild; + + @ManyToOne() + @JoinColumn(name="CODESYSTEM_PID", nullable=false, foreignKey=@ForeignKey(name="FK_TERM_CONCEPTPC_CS")) + private TermCodeSystemVersion myCodeSystem; + + @ManyToOne() + @JoinColumn(name="PARENT_PID", nullable=false, referencedColumnName="PID", foreignKey=@ForeignKey(name="FK_TERM_CONCEPTPC_PARENT")) + private TermConcept myParent; + @Id() @SequenceGenerator(name="SEQ_CONCEPT_PC_PID", sequenceName="SEQ_CONCEPT_PC_PID") - @GeneratedValue() + @GeneratedValue(strategy=GenerationType.AUTO, generator="SEQ_CONCEPT_PC_PID") @Column(name="PID") private Long myPid; - @ManyToOne - @JoinColumn(name="PARENT_PID", nullable=false) - private TermConcept myParent; - - @ManyToOne - @JoinColumn(name="CHILD_PID", nullable=false) - private TermConcept myChild; + public TermConcept getChild() { + return myChild; + } - @ManyToOne - @JoinColumn(name="CODESYSTEM_PID", nullable=false) - private TermCodeSystem myCodeSystem; + public TermCodeSystemVersion getCodeSystem() { + return myCodeSystem; + } + + public TermConcept getParent() { + return myParent; + } + + public void setChild(TermConcept theChild) { + myChild = theChild; + } + + public void setCodeSystem(TermCodeSystemVersion theCodeSystem) { + myCodeSystem = theCodeSystem; + } + + public void setParent(TermConcept theParent) { + myParent = theParent; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ITerminologySvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ITerminologySvc.java index 26d7bfccd24..5903ddbb732 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ITerminologySvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ITerminologySvc.java @@ -1,5 +1,14 @@ package ca.uhn.fhir.jpa.term; +import java.util.Set; + +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; + public interface ITerminologySvc { + void storeNewCodeSystemVersion(String theSystemUri, TermCodeSystemVersion theCodeSytem); + + Set findCodesBelow(Long theCodeSystemResourcePid, Long theCodeSystemResourceVersion, String theCode); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologySvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologySvcImpl.java index ffd0dc7652f..5e5a6702830 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologySvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologySvcImpl.java @@ -1,6 +1,136 @@ package ca.uhn.fhir.jpa.term; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Set; + +import javax.transaction.Transactional; +import javax.transaction.Transactional.TxType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.ValidationUtils; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.ObjectUtil; +import ca.uhn.fhir.util.ValidateUtil; + public class TerminologySvcImpl implements ITerminologySvc { + private static final Object PLACEHOLDER_OBJECT = new Object(); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologySvcImpl.class); + + @Autowired + private ITermCodeSystemVersionDao myCodeSystemVersionDao; + + @Autowired + private ITermConceptParentChildLinkDao myConceptParentChildLinkDao; + + @Autowired + private ITermConceptDao myConceptDao; + + @Autowired + private ITermCodeSystemDao myCodeSystemDao; + + @Autowired + private FhirContext myContext; + + + @Override + @Transactional(value=TxType.REQUIRED) + public void storeNewCodeSystemVersion(String theSystemUri, TermCodeSystemVersion theCodeSystem) { + ourLog.info("Storing code system"); + + ValidateUtil.isNotNullOrThrowInvalidRequest(theCodeSystem.getResource() != null, "No resource supplied"); + ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied"); + + TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theSystemUri); + if (codeSystem == null) { + TermCodeSystem newCodeSystem = new TermCodeSystem(); + newCodeSystem.setResource(theCodeSystem.getResource()); + newCodeSystem.setCodeSystemUri(theSystemUri); + myCodeSystemDao.save(newCodeSystem); + } else { + if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystem.getResource().getId())) { + throw new InvalidRequestException(myContext.getLocalizer().getMessage(TerminologySvcImpl.class, "cannotCreateDuplicateCodeSystemUri", theSystemUri, codeSystem.getResource().getIdDt().getValue())); + } + } + + // Validate the code system + IdentityHashMap conceptsStack = new IdentityHashMap(); + for (TermConcept next : theCodeSystem.getConcepts()) { + validateConceptForStorage(next, theCodeSystem, conceptsStack); + } + + myCodeSystemVersionDao.save(theCodeSystem); + + conceptsStack = new IdentityHashMap(); + for (TermConcept next : theCodeSystem.getConcepts()) { + persistChildren(next, theCodeSystem, conceptsStack); + } + } + + private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap theConceptsStack) { + if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) { + return; + } + + for (TermConceptParentChildLink next : theConcept.getChildren()) { + persistChildren(next.getChild(), theCodeSystem, theConceptsStack); + } + + myConceptDao.save(theConcept); + + for (TermConceptParentChildLink next : theConcept.getChildren()) { + myConceptParentChildLinkDao.save(next); + } + } + + private void validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap theConceptsStack) { + ValidateUtil.isNotNullOrThrowInvalidRequest(theConcept.getCodeSystem() == theCodeSystem, "Codesystem contains a code which does not reference the codesystem"); + ValidateUtil.isNotBlankOrThrowInvalidRequest(theConcept.getCode(), "Codesystem contains a code which does not reference the codesystem"); + + if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) { + throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode()); + } + + for (TermConceptParentChildLink next : theConcept.getChildren()) { + validateConceptForStorage(next.getChild(), theCodeSystem, theConceptsStack); + } + + theConceptsStack.remove(theConcept); + } + + @Transactional(value=TxType.REQUIRED) + @Override + public Set findCodesBelow(Long theCodeSystemResourcePid, Long theCodeSystemVersionPid, String theCode) { + TermCodeSystemVersion codeSystem = myCodeSystemVersionDao.findByCodeSystemResourceAndVersion(theCodeSystemResourcePid, theCodeSystemVersionPid); + TermConcept concept = myConceptDao.findByCodeSystemAndCode(codeSystem, theCode); + + Set retVal = new HashSet(); + retVal.add(concept); + + fetchChildren(concept, retVal); + + return retVal; + } + + private void fetchChildren(TermConcept theConcept, Set theSetToPopulate) { + for (TermConceptParentChildLink nextChildLink : theConcept.getChildren()) { + TermConcept nextChild = nextChildLink.getChild(); + if (theSetToPopulate.add(nextChild)) { + fetchChildren(nextChild, theSetToPopulate); + } + } + } + diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index 398d137f2c8..ffb25854525 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -13,6 +13,7 @@ import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.ConceptMap; @@ -61,6 +62,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.ISearchDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; import ca.uhn.fhir.jpa.entity.ForcedId; import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; @@ -78,7 +80,12 @@ import ca.uhn.fhir.jpa.entity.ResourceTag; import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource; import ca.uhn.fhir.jpa.entity.SubscriptionTable; import ca.uhn.fhir.jpa.entity.TagDefinition; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; +import ca.uhn.fhir.jpa.term.ITerminologySvc; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.method.MethodUtil; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @@ -88,6 +95,10 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @ContextConfiguration(classes= {TestDstu3Config.class}) //@formatter:on public abstract class BaseJpaDstu3Test extends BaseJpaTest { + @Autowired + protected IResourceTableDao myResourceTableDao; + @Autowired + protected ITerminologySvc myTermSvc; @Autowired @Qualifier("myJpaValidationSupportChainDstu3") protected IValidationSupport myValidationSupport; @@ -99,6 +110,9 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Qualifier("myConceptMapDaoDstu3") protected IFhirResourceDao myConceptMapDao; @Autowired + @Qualifier("myCodeSystemDaoDstu3") + protected IFhirResourceDao myCodeSystemDao; + @Autowired @Qualifier("myMedicationDaoDstu3") protected IFhirResourceDao myMedicationDao; @Autowired @@ -241,6 +255,16 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { return null; } }); + txTemplate.execute(new TransactionCallback() { + @Override + public Void doInTransaction(TransactionStatus theStatus) { + entityManager.createQuery("DELETE from " + TermConceptParentChildLink.class.getSimpleName() + " d").executeUpdate(); + entityManager.createQuery("DELETE from " + TermConcept.class.getSimpleName() + " d").executeUpdate(); + entityManager.createQuery("DELETE from " + TermCodeSystemVersion.class.getSimpleName() + " d").executeUpdate(); + entityManager.createQuery("DELETE from " + TermCodeSystem.class.getSimpleName() + " d").executeUpdate(); + return null; + } + }); txTemplate.execute(new TransactionCallback() { @Override public Void doInTransaction(TransactionStatus theStatus) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java new file mode 100644 index 00000000000..404398e084e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java @@ -0,0 +1,151 @@ +package ca.uhn.fhir.jpa.term; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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.junit.Test; + +import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class TerminologySvcImplTest extends BaseJpaDstu3Test { + + @Test + public void testStoreCodeSystemInvalidCyclicLoop() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl("http://example.com/my_code_system"); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + + TermConcept parent = new TermConcept(); + parent.setCodeSystem(cs); + parent.setCode("parent"); + cs.getConcepts().add(parent); + + TermConcept child = new TermConcept(); + child.setCodeSystem(cs); + child.setCode("child"); + parent.addChild(child); + + child.addChild(parent); + + try { + myTermSvc.storeNewCodeSystemVersion("http://foo", cs); + fail(); + } catch (InvalidRequestException e) { + assertEquals("CodeSystem contains circular reference around code parent", e.getMessage()); + } + } + + @Test + public void testFetchIsA() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl("http://example.com/my_code_system"); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + + TermConcept parentA = new TermConcept(cs, "ParentA"); + cs.getConcepts().add(parentA); + + TermConcept childAA = new TermConcept(cs, "childAA"); + parentA.addChild(childAA); + + TermConcept childAAA = new TermConcept(cs, "childAAA"); + childAA.addChild(childAAA); + + TermConcept childAAB = new TermConcept(cs, "childAAB"); + childAA.addChild(childAAB); + + TermConcept childAB = new TermConcept(cs, "childAB"); + parentA.addChild(childAB); + + TermConcept parentB = new TermConcept(cs, "ParentB"); + cs.getConcepts().add(parentB); + + myTermSvc.storeNewCodeSystemVersion("http://foo", cs); + + Set concepts; + Set codes; + + concepts = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "ParentA"); + codes = toCodes(concepts); + assertThat(codes, containsInAnyOrder("ParentA", "childAA", "childAAA", "childAAB", "childAB")); + + concepts = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "childAA"); + codes = toCodes(concepts); + assertThat(codes, containsInAnyOrder("childAA", "childAAA", "childAAB")); + } + + @Test + public void testCreateDuplicateCodeSystemUri() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl("http://example.com/my_code_system"); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + + myTermSvc.storeNewCodeSystemVersion("http://example.com/my_code_system", cs); + + // Update + cs = new TermCodeSystemVersion(); + TermConcept parentA = new TermConcept(cs, "ParentA"); + cs.getConcepts().add(parentA); + id = myCodeSystemDao.update(codeSystem).getId().toUnqualified(); + table = myResourceTableDao.findOne(id.getIdPartAsLong()); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + myTermSvc.storeNewCodeSystemVersion("http://example.com/my_code_system", cs); + + // Try to update to a different resource + codeSystem = new CodeSystem(); + codeSystem.setUrl("http://example.com/my_code_system"); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + id = myCodeSystemDao.create(codeSystem).getId().toUnqualified(); + table = myResourceTableDao.findOne(id.getIdPartAsLong()); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + try { + myTermSvc.storeNewCodeSystemVersion("http://example.com/my_code_system", cs); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/")); + } + + } + + private Set toCodes(Set theConcepts) { + HashSet retVal = new HashSet(); + for (TermConcept next : theConcepts) { + retVal.add(next.getCode()); + } + return retVal; + } +} diff --git a/hapi-fhir-structures-dstu3/src/main/resources/org/hl7/fhir/dstu3/model/fhirversion.properties b/hapi-fhir-structures-dstu3/src/main/resources/org/hl7/fhir/dstu3/model/fhirversion.properties index 8c1ea05b949..c55ce02715e 100644 --- a/hapi-fhir-structures-dstu3/src/main/resources/org/hl7/fhir/dstu3/model/fhirversion.properties +++ b/hapi-fhir-structures-dstu3/src/main/resources/org/hl7/fhir/dstu3/model/fhirversion.properties @@ -13,6 +13,7 @@ resource.CarePlan=org.hl7.fhir.dstu3.model.CarePlan resource.Claim=org.hl7.fhir.dstu3.model.Claim resource.ClaimResponse=org.hl7.fhir.dstu3.model.ClaimResponse resource.ClinicalImpression=org.hl7.fhir.dstu3.model.ClinicalImpression +resource.CodeSystem=org.hl7.fhir.dstu3.model.CodeSystem resource.Communication=org.hl7.fhir.dstu3.model.Communication resource.CommunicationRequest=org.hl7.fhir.dstu3.model.CommunicationRequest resource.Composition=org.hl7.fhir.dstu3.model.Composition @@ -54,6 +55,7 @@ resource.Immunization=org.hl7.fhir.dstu3.model.Immunization resource.ImmunizationRecommendation=org.hl7.fhir.dstu3.model.ImmunizationRecommendation resource.ImplementationGuide=org.hl7.fhir.dstu3.model.ImplementationGuide resource.Library=org.hl7.fhir.dstu3.model.Library +resource.Linkage=org.hl7.fhir.dstu3.model.Linkage resource.List=org.hl7.fhir.dstu3.model.ListResource resource.Location=org.hl7.fhir.dstu3.model.Location resource.Measure=org.hl7.fhir.dstu3.model.Measure @@ -85,6 +87,7 @@ resource.Procedure=org.hl7.fhir.dstu3.model.Procedure resource.ProcedureRequest=org.hl7.fhir.dstu3.model.ProcedureRequest resource.ProcessRequest=org.hl7.fhir.dstu3.model.ProcessRequest resource.ProcessResponse=org.hl7.fhir.dstu3.model.ProcessResponse +resource.Protocol=org.hl7.fhir.dstu3.model.Protocol resource.Provenance=org.hl7.fhir.dstu3.model.Provenance resource.Questionnaire=org.hl7.fhir.dstu3.model.Questionnaire resource.QuestionnaireResponse=org.hl7.fhir.dstu3.model.QuestionnaireResponse diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java index 29b00e55e34..6ee11880454 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java @@ -18,7 +18,6 @@ import java.util.List; import java.util.UUID; import org.apache.commons.io.IOUtils; -import org.eclipse.jetty.util.IO; import org.hamcrest.Matchers; import org.hl7.fhir.dstu3.model.Binary; import org.hl7.fhir.dstu3.model.Bundle; @@ -37,6 +36,7 @@ import org.hl7.fhir.dstu3.model.Extension; import org.hl7.fhir.dstu3.model.HumanName; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse; +import org.hl7.fhir.dstu3.model.Linkage; import org.hl7.fhir.dstu3.model.Medication; import org.hl7.fhir.dstu3.model.MedicationOrder; import org.hl7.fhir.dstu3.model.Observation; @@ -46,7 +46,6 @@ import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Quantity; import org.hl7.fhir.dstu3.model.QuestionnaireResponse; import org.hl7.fhir.dstu3.model.Reference; -import org.hl7.fhir.dstu3.model.Specimen; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; @@ -71,6 +70,15 @@ public class JsonParserDstu3Test { ourCtx.setNarrativeGenerator(null); } + @Test + public void testLinkage() { + Linkage l = new Linkage(); + l.addItem().getResource().setDisplay("FOO"); + String out = ourCtx.newXmlParser().encodeResourceToString(l); + ourLog.info(out); + assertEquals("", out); + } + @Test public void testEncodeAndParseExtensions() throws Exception { diff --git a/hapi-tinder-plugin/.classpath b/hapi-tinder-plugin/.classpath index fafda0399f3..4efeb48e6cf 100644 --- a/hapi-tinder-plugin/.classpath +++ b/hapi-tinder-plugin/.classpath @@ -22,6 +22,7 @@ +