From eba136d70623c0069d517e13c5dc84a748e8af4a Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 27 Sep 2016 14:22:48 -0400 Subject: [PATCH] Correctly handle custom types in programatic access to JPA --- .../java/ca/uhn/fhir/context/FhirContext.java | 9 ++-- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 43 +++++++++++++++--- .../jpa/dao/dstu3/CustomObservationDstu3.java | 29 ++++++++++++ .../FhirResourceDaoCustomTypeDstu3Test.java | 45 +++++++++++++++++++ .../FhirResourceDaoDocumentDstu3Test.java | 5 ++- .../FhirResourceDaoDstu3SearchNoFtTest.java | 15 +++++++ .../src/test/resources/bug454_utf8.json | 16 +++++++ .../derby_maintenance.txt | 2 + src/changes/changes.xml | 5 +++ 9 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/CustomObservationDstu3.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCustomTypeDstu3Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/bug454_utf8.json diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index e6e52bf0522..900a54d19d3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -694,12 +694,15 @@ public void setAddProfileTagWhenEncoding(AddProfileTagEnum theAddProfileTagWhenE * The profile string, e.g. "http://example.com/some_patient_profile". Must not be * null or empty. * @param theClass - * The resource type. Must not be null or empty. + * The resource type, or null to clear any existing type */ public void setDefaultTypeForProfile(String theProfile, Class theClass) { Validate.notBlank(theProfile, "theProfile must not be null or empty"); - Validate.notNull(theClass, "theProfile must not be null"); - myDefaultTypeForProfile.put(theProfile, theClass); + if (theClass == null) { + myDefaultTypeForProfile.remove(theProfile); + } else { + myDefaultTypeForProfile.put(theProfile, theClass); + } } /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 3ee157f83f3..044c76737fd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -733,6 +733,17 @@ protected void populateResourceIntoEntity(IBaseResource theResource, ResourceTab extractTagsRi((IAnyResource) theResource, theEntity, allDefs); } + RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); + if (def.isStandardType() == false) { + String profile = def.getResourceProfile(""); + if (isNotBlank(profile)) { + TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null); + allDefs.add(tag); + theEntity.addTag(tag); + theEntity.setHasTags(true); + } + } + ArrayList existingTags = new ArrayList(); if (theEntity.isHasTags()) { existingTags.addAll(theEntity.getTags()); @@ -1003,9 +1014,11 @@ protected ResourceTable toEntity(IResource theResource) { @Override public IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) { RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType()); - return toResource(type.getImplementingClass(), theEntity, theForHistoryOperation); + Class resourceType = type.getImplementingClass(); + return toResource(resourceType, theEntity, theForHistoryOperation); } + @SuppressWarnings("unchecked") @Override public R toResource(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation) { String resourceText = null; @@ -1022,14 +1035,34 @@ public R toResource(Class theResourceType, BaseHasR break; } + /* + * Use the appropriate custom type if one is specified in the context + */ + Class resourceType = theResourceType; + if (myContext.hasDefaultTypeForProfile()) { + for (BaseTag nextTag : theEntity.getTags()) { + if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) { + String profile = nextTag.getTag().getCode(); + if (isNotBlank(profile)) { + Class newType = myContext.getDefaultTypeForProfile(profile); + if (newType != null && theResourceType.isAssignableFrom(newType)) { + ourLog.debug("Using custom type {} for profile: {}", newType.getName(), profile); + resourceType = (Class) newType; + break; + } + } + } + } + } + IParser parser = theEntity.getEncoding().newParser(getContext(theEntity.getFhirVersion())); R retVal; try { - retVal = parser.parseResource(theResourceType, resourceText); + retVal = parser.parseResource(resourceType, resourceText); } catch (Exception e) { StringBuilder b = new StringBuilder(); b.append("Failed to parse database resource["); - b.append(theResourceType); + b.append(resourceType); b.append("/"); b.append(theEntity.getIdDt().getIdPart()); b.append(" (pid "); @@ -1045,10 +1078,10 @@ public R toResource(Class theResourceType, BaseHasR if (retVal instanceof IResource) { IResource res = (IResource) retVal; - retVal = populateResourceMetadataHapi(theResourceType, theEntity, theForHistoryOperation, res); + retVal = populateResourceMetadataHapi(resourceType, theEntity, theForHistoryOperation, res); } else { IAnyResource res = (IAnyResource) retVal; - retVal = populateResourceMetadataRi(theResourceType, theEntity, theForHistoryOperation, res); + retVal = populateResourceMetadataRi(resourceType, theEntity, theForHistoryOperation, res); } return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/CustomObservationDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/CustomObservationDstu3.java new file mode 100644 index 00000000000..d934433296a --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/CustomObservationDstu3.java @@ -0,0 +1,29 @@ +package ca.uhn.fhir.jpa.dao.dstu3; + +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.StringType; + +import ca.uhn.fhir.model.api.annotation.Child; +import ca.uhn.fhir.model.api.annotation.Extension; +import ca.uhn.fhir.model.api.annotation.ResourceDef; + +@ResourceDef(name = "Observation", profile = CustomObservationDstu3.PROFILE) +public class CustomObservationDstu3 extends Observation { + + public static final String PROFILE = "http://custom_ObservationDstu3"; + + private static final long serialVersionUID = 1L; + + @Extension(definedLocally = false, isModifier = false, url = "http://eyeColour") + @Child(name = "eyeColour") + private StringType myEyeColour; + + public StringType getEyeColour() { + return myEyeColour; + } + + public void setEyeColour(StringType theEyeColour) { + myEyeColour = theEyeColour; + } + +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCustomTypeDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCustomTypeDstu3Test.java new file mode 100644 index 00000000000..d53fca64e2d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCustomTypeDstu3Test.java @@ -0,0 +1,45 @@ +package ca.uhn.fhir.jpa.dao.dstu3; + +import static org.junit.Assert.assertEquals; + +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.rest.server.IBundleProvider; + +@SuppressWarnings({ }) +public class FhirResourceDaoCustomTypeDstu3Test extends BaseJpaDstu3Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoCustomTypeDstu3Test.class); + + @Before + public void before() { + myFhirCtx.setDefaultTypeForProfile(CustomObservationDstu3.PROFILE, CustomObservationDstu3.class); + } + + @Test + public void testSaveAndRestore() { + CustomObservationDstu3 obs = new CustomObservationDstu3(); + obs.setEyeColour(new StringType("blue")); + + IIdType id = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + CustomObservationDstu3 read = (CustomObservationDstu3) myObservationDao.read(id); + assertEquals("blue", read.getEyeColour().getValue()); + + IBundleProvider found = myObservationDao.search(new SearchParameterMap()); + assertEquals(1, found.size()); + CustomObservationDstu3 search = (CustomObservationDstu3) found.getResources(0, 1).get(0); + assertEquals("blue", search.getEyeColour().getValue()); + + } + + @After + public void after() { + myFhirCtx.setDefaultTypeForProfile(CustomObservationDstu3.PROFILE, null); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java index 0b1684c19a9..a9370b6e83a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; +import java.nio.charset.StandardCharsets; + import org.apache.commons.io.IOUtils; import org.hl7.fhir.dstu3.model.Bundle; import org.junit.AfterClass; @@ -8,7 +10,6 @@ import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; import ca.uhn.fhir.util.TestUtil; -@SuppressWarnings("unchecked") public class FhirResourceDaoDocumentDstu3Test extends BaseJpaDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDocumentDstu3Test.class); @@ -21,7 +22,7 @@ public static void afterClassClearContext() { @Test public void testPostDocument() throws Exception { - String input = IOUtils.toString(getClass().getResourceAsStream("/sample-document.xml")); + String input = IOUtils.toString(getClass().getResourceAsStream("/sample-document.xml"), StandardCharsets.UTF_8); Bundle inputBundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); DaoMethodOutcome responseBundle = myBundleDao.create(inputBundle, mySrd); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java index 6be0bd893a7..2a007253dc3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java @@ -13,11 +13,14 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; +import java.io.IOException; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.util.*; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; @@ -81,6 +84,18 @@ public IIdType doInTransaction(TransactionStatus theStatus) { assertEquals(Patient.class, foundResources.get(0).getClass()); assertEquals(Condition.class, foundResources.get(1).getClass()); } + + /** + * #454 + */ + @Test + public void testIndexWithUtf8Chars() throws IOException { + String input = IOUtils.toString(getClass().getResourceAsStream("/bug454_utf8.json"), StandardCharsets.UTF_8); + + CodeSystem cs = (CodeSystem) myFhirCtx.newJsonParser().parseResource(input); + myCodeSystemDao.create(cs); + } + @Test public void testCodeSearch() { diff --git a/hapi-fhir-jpaserver-base/src/test/resources/bug454_utf8.json b/hapi-fhir-jpaserver-base/src/test/resources/bug454_utf8.json new file mode 100644 index 00000000000..ff362522f48 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/bug454_utf8.json @@ -0,0 +1,16 @@ +{ + "resourceType": "CodeSystem", + "url": "http://nestvision.com/fhir/myexample-yh", + "name": "NestVision Restful Interactions", + "status": "active", + "publisher": "杨浩", + "description": "this is a codesystem example.", + "caseSensitive": true, + "concept": [ + { + "code": "mygod", + "display": "wodetian", + "definition": "this is a translate." + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt b/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt index 7864fd0dd8e..626a2adeab3 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt +++ b/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt @@ -1,3 +1,5 @@ call SYSCS_UTIL.SYSCS_COMPRESS_TABLE('SA', 'HFJ_SEARCH_RESULT', 1); CALL SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE( 'SA', 'HFJ_SEARCH_RESULT', 0, 0, 1 ); + +dd if=/dev/urandom of=/opt/glassfish/tmp.tmp bs=1M count=500 diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 28da318b75a..eee67402d6c 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -102,6 +102,11 @@ JAX-RS server was not able to handle the new mime types defined in STU3 + + JPA server did not handle custom types when being called + programatically (I.e. not through HTTP interface). Thanks to + Anthony Mei for pointing this out! +