From 6f7ef96b975375a8542a125c61f3bb2d34f8139a Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 16 Jun 2015 11:56:30 -0400 Subject: [PATCH] Support $validate operation correctly in DSTU2 clients and in testpage overlay --- .../ca/uhn/fhir/model/primitive/IdDt.java | 55 +- .../ca/uhn/fhir/rest/client/BaseClient.java | 2 +- .../rest/client/BaseHttpClientInvocation.java | 2 +- .../uhn/fhir/rest/client/GenericClient.java | 20 +- .../BaseHttpClientInvocationWithContents.java | 6 +- .../method/HttpDeleteClientInvocation.java | 2 +- .../rest/method/HttpGetClientInvocation.java | 2 +- .../method/HttpSimpleGetClientInvocation.java | 2 +- .../uhn/fhir/rest/param/ReferenceParam.java | 6 +- .../java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java | 4 +- .../uhn/fhir/jpa/dao/BaseFhirResourceDao.java | 19 +- .../jpa/dao/FhirBundleResourceDaoDstu2.java | 24 + .../uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java | 18 +- .../uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java | 33 +- .../ca/uhn/fhir/jpa/entity/ResourceLink.java | 8 +- .../provider/JpaResourceProviderDstu2.java | 2 +- .../jpa/dao/FhirResourceDaoDstu2Test.java | 6 + .../fhir/jpa/dao/FhirSystemDaoDstu1Test.java | 5 +- .../fhir/jpa/dao/FhirSystemDaoDstu2Test.java | 4 +- .../provider/ResourceProviderDstu2Test.java | 29 ++ .../src/test/resources/document-father.json | 475 ++++++++++++++++++ .../ca/uhn/fhir/model/primitive/IdDtTest.java | 43 +- .../ca/uhn/fhir/parser/JsonParserTest.java | 2 +- .../ca/uhn/fhir/parser/XmlParserTest.java | 2 + .../uhn/fhir/parser/JsonParserDstu2Test.java | 92 +++- .../rest/client/GenericClientDstu2Test.java | 10 +- .../org/hl7/fhir/instance/model/IdType.java | 53 +- .../java/ca/uhn/fhir/model/IdTypeTest.java | 226 +++++++++ .../main/java/ca/uhn/fhir/to/Controller.java | 5 +- .../fhir/tinder/TinderJpaRestServerMojo.java | 3 - .../resources/vm/jpa_resource_provider.vm | 5 +- .../src/main/resources/vm/jpa_spring_beans.vm | 7 +- restful-server-example/pom.xml | 2 +- 33 files changed, 1076 insertions(+), 98 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirBundleResourceDaoDstu2.java create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/document-father.json create mode 100644 hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/model/IdTypeTest.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java index ba9a72e7c45..5bc0efa6448 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java @@ -283,6 +283,11 @@ public class IdDt extends UriDt implements IPrimitiveDatatype, IIdType { @Override public String getValue() { if (super.getValue() == null && myHaveComponentParts) { + + if (determineLocalPrefix(myBaseUrl) != null && myResourceType == null && myUnqualifiedVersionId == null) { + return myBaseUrl + myUnqualifiedId; + } + StringBuilder b = new StringBuilder(); if (isNotBlank(myBaseUrl)) { b.append(myBaseUrl); @@ -395,11 +400,15 @@ public class IdDt extends UriDt implements IPrimitiveDatatype, IIdType { } /** - * Returns true if the ID is a local reference (in other words, it begins with the '#' character) + * Returns true if the ID is a local reference (in other words, it begins with the '#' character + * or it begins with "cid:" or "urn:") */ @Override public boolean isLocal() { - return myUnqualifiedId != null && myUnqualifiedId.isEmpty() == false && myUnqualifiedId.charAt(0) == '#'; + if (myBaseUrl == null) { + return false; + } + return "#".equals(myBaseUrl) || myBaseUrl.equals("cid:") || myBaseUrl.startsWith("urn:"); } /** @@ -410,6 +419,34 @@ public class IdDt extends UriDt implements IPrimitiveDatatype, IIdType { setValue(theId.getValue()); } + private String determineLocalPrefix(String theValue) { + if (theValue == null || theValue.isEmpty()) { + return null; + } + if (theValue.startsWith("#")) { + return "#"; + } + int lastPrefix = -1; + for (int i = 0; i < theValue.length(); i++) { + char nextChar = theValue.charAt(i); + if (nextChar == ':') { + lastPrefix = i; + } else if (!Character.isLetter(nextChar) || !Character.isLowerCase(nextChar)) { + break; + } + } + if (lastPrefix != -1) { + String candidate = theValue.substring(0, lastPrefix + 1); + if (candidate.startsWith("cid:") || candidate.startsWith("urn:")) { + return candidate; + } else { + return null; + } + } else { + return null; + } + } + /** * Set the value * @@ -426,15 +463,25 @@ public class IdDt extends UriDt implements IPrimitiveDatatype, IIdType { // TODO: add validation super.setValue(theValue); myHaveComponentParts = false; + + String localPrefix = determineLocalPrefix(theValue); + if (StringUtils.isBlank(theValue)) { myBaseUrl = null; super.setValue(null); myUnqualifiedId = null; myUnqualifiedVersionId = null; myResourceType = null; - } else if (theValue.charAt(0) == '#') { + } else if (theValue.charAt(0) == '#' && theValue.length() > 1) { super.setValue(theValue); - myUnqualifiedId = theValue; + myBaseUrl = "#"; + myUnqualifiedId = theValue.substring(1); + myUnqualifiedVersionId = null; + myResourceType = null; + myHaveComponentParts = true; + } else if (localPrefix != null) { + myBaseUrl = localPrefix; + myUnqualifiedId = theValue.substring(localPrefix.length()); myUnqualifiedVersionId = null; myResourceType = null; myHaveComponentParts = true; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java index 08cbd27f5b2..b08cb731599 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java @@ -186,7 +186,7 @@ public abstract class BaseClient implements IRestfulClient { encoding=theEncoding; } - httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding); + httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint); if (theLogRequestAndResponse) { ourLog.info("Client invoking: {}", httpRequest); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java index 0cd4a316e8e..7c4a708695f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java @@ -56,7 +56,7 @@ public abstract class BaseHttpClientInvocation { * The encoding to use for any serialized content sent to the * server */ - public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding); + public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint); protected static void appendExtraParamsWithQuestionMark(Map> theExtraParams, StringBuilder theUrlBuilder, boolean theWithQuestionMark) { if (theExtraParams == null) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index 519df9085dd..06f3595fa49 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -158,7 +158,7 @@ public class GenericClient extends BaseClient implements IGenericClient { HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(); if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } @SuppressWarnings("unchecked") @@ -178,7 +178,7 @@ public class GenericClient extends BaseClient implements IGenericClient { public MethodOutcome create(IResource theResource) { BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(theResource, myContext); if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); @@ -200,7 +200,7 @@ public class GenericClient extends BaseClient implements IGenericClient { public MethodOutcome delete(final Class theType, IdDt theId) { HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType))); if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } final String resourceName = myContext.getResourceDefinition(theType).getName(); @@ -236,7 +236,7 @@ public class GenericClient extends BaseClient implements IGenericClient { } } if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } if (theIfVersionMatches != null) { @@ -312,7 +312,7 @@ public class GenericClient extends BaseClient implements IGenericClient { String id = theIdDt != null && theIdDt.isEmpty() == false ? theIdDt.getValue() : null; HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, theSince, theLimit); if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } BundleResponseHandler binding = new BundleResponseHandler(theType); @@ -430,7 +430,7 @@ public class GenericClient extends BaseClient implements IGenericClient { BaseHttpClientInvocation invocation = SearchMethodBinding.createSearchInvocation(myContext, toResourceName(theType), params, null, null, null); if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } BundleResponseHandler binding = new BundleResponseHandler(theType); @@ -474,7 +474,7 @@ public class GenericClient extends BaseClient implements IGenericClient { public List transaction(List theResources) { BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(theResources, myContext); if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } Bundle resp = invokeClient(myContext, new BundleResponseHandler(null), invocation, myLogRequestAndResponse); @@ -491,7 +491,7 @@ public class GenericClient extends BaseClient implements IGenericClient { public MethodOutcome update(IdDt theIdDt, IResource theResource) { BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext); if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); @@ -522,7 +522,7 @@ public class GenericClient extends BaseClient implements IGenericClient { } if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); @@ -601,7 +601,7 @@ public class GenericClient extends BaseClient implements IGenericClient { // } if (isKeepResponses()) { - myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding()); + myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint); } Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java index 9f4a05c5898..00823befecb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java @@ -188,7 +188,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } @Override - public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding) throws DataFormatException { + public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) throws DataFormatException { StringBuilder url = new StringBuilder(); if (myUrlPath == null) { @@ -236,6 +236,10 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca parser = myContext.newXmlParser(); } + if (thePrettyPrint != null) { + parser.setPrettyPrint(thePrettyPrint); + } + parser.setOmitResourceId(myOmitResourceId); AbstractHttpEntity entity; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java index 02471c13751..374c0d66501 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java @@ -50,7 +50,7 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { } @Override - public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding) { + public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { StringBuilder b = new StringBuilder(); b.append(theUrlBase); if (!theUrlBase.endsWith("/")) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpGetClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpGetClientInvocation.java index e76370c58c4..b9276db009f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpGetClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpGetClientInvocation.java @@ -79,7 +79,7 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation { } @Override - public HttpGet asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding) { + public HttpGet asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { StringBuilder b = new StringBuilder(); if (!myUrlPath.contains("://")) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpSimpleGetClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpSimpleGetClientInvocation.java index 4aabade3d47..7a68a8497c4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpSimpleGetClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpSimpleGetClientInvocation.java @@ -38,7 +38,7 @@ public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation { } @Override - public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding) { + public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { HttpGet retVal = new HttpGet(myUrl); super.addHeadersToRequest(retVal); return retVal; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java index 6e4f2899319..62422f01d0f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java @@ -85,7 +85,11 @@ public class ReferenceParam extends IdDt implements IQueryParameterType { if (myBase.getMissing()!=null) { return myBase.getValueAsQueryToken(); } - return getIdPart(); + if (isLocal()) { + return getValue(); + } else { + return getIdPart(); + } } public void setChain(String theChain) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java index 767306b7d2e..e6881c8bbdc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java @@ -1048,8 +1048,8 @@ public abstract class BaseFhirDao implements IDao { quantityParams = extractSearchParamQuantity(entity, theResource); dateParams = extractSearchParamDates(entity, theResource); - ourLog.info("Indexing resource: {}", entity.getId()); - ourLog.info("Storing string indexes: {}", stringParams); +// ourLog.info("Indexing resource: {}", entity.getId()); + ourLog.trace("Storing string indexes: {}", stringParams); tokenParams = new ArrayList(); for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(entity, theResource)) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirResourceDao.java index fb637cf7e35..41b20daca1e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirResourceDao.java @@ -574,12 +574,12 @@ public abstract class BaseFhirResourceDao extends BaseFhirD ReferenceParam ref = (ReferenceParam) params; String resourceId = ref.getValueAsQueryToken(); - if (resourceId.contains("/")) { - IdDt dt = new IdDt(resourceId); - resourceId = dt.getIdPart(); - } if (isBlank(ref.getChain())) { + if (resourceId.contains("/")) { + IdDt dt = new IdDt(resourceId); + resourceId = dt.getIdPart(); + } Long targetPid = translateForcedIdToPid(new IdDt(resourceId)); ourLog.info("Searching for resource link with target PID: {}", targetPid); Predicate eq = builder.equal(from.get("myTargetResourcePid"), targetPid); @@ -1097,6 +1097,9 @@ public abstract class BaseFhirResourceDao extends BaseFhirD private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing) { StopWatch w = new StopWatch(); + + preProcessResourceForStorage(theResource); + ResourceTable entity = new ResourceTable(); entity.setResourceType(toResourceName(theResource)); @@ -1112,7 +1115,7 @@ public abstract class BaseFhirResourceDao extends BaseFhirD } } - if (theResource.getId().isEmpty() == false) { + if (isNotBlank(theResource.getId().getIdPart())) { if (isValidPid(theResource.getId())) { throw new UnprocessableEntityException( "This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID"); @@ -1139,6 +1142,10 @@ public abstract class BaseFhirResourceDao extends BaseFhirD return outcome; } + protected void preProcessResourceForStorage(T theResource) { + // nothing by default + } + @Override public TagList getAllResourceTags() { StopWatch w = new StopWatch(); @@ -1995,6 +2002,8 @@ public abstract class BaseFhirResourceDao extends BaseFhirD public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing) { StopWatch w = new StopWatch(); + preProcessResourceForStorage(theResource); + final ResourceTable entity; IdDt resourceId; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirBundleResourceDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirBundleResourceDaoDstu2.java new file mode 100644 index 00000000000..238b4fac512 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirBundleResourceDaoDstu2.java @@ -0,0 +1,24 @@ +package ca.uhn.fhir.jpa.dao; + +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum; +import ca.uhn.fhir.model.primitive.UriDt; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; + +public class FhirBundleResourceDaoDstu2 extends FhirResourceDaoDstu2 { + + @Override + protected void preProcessResourceForStorage(Bundle theResource) { + super.preProcessResourceForStorage(theResource); + + if (theResource.getTypeElement().getValueAsEnum() != BundleTypeEnum.DOCUMENT) { + String message = "Unable to store a Bundle resource on this server with a Bundle.type value other than '" + BundleTypeEnum.DOCUMENT.getCode() + "' - Value was: " + (theResource.getTypeElement().getValueAsEnum() != null ? theResource.getTypeElement().getValueAsEnum().getCode() : "(missing)"); + throw new UnprocessableEntityException(message); + } + + theResource.setBase((UriDt)null); + } + + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java index 77c29af3927..7bfc0566cf2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java @@ -60,14 +60,18 @@ public class FhirSystemDaoDstu1 extends BaseFhirSystemDao> { for (int i = 0; i < theResources.size(); i++) { IResource res = theResources.get(i); - if (res.getId().hasIdPart() && !res.getId().hasResourceType()) { + if (res.getId().hasIdPart() && !res.getId().hasResourceType() && !res.getId().isLocal()) { res.setId(new IdDt(toResourceName(res.getClass()), res.getId().getIdPart())); } /* * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness */ - if (res.getId().hasResourceType() && res.getId().hasIdPart()) { + if (res.getId().isLocal()) { + if (!allIds.add(res.getId())) { + throw new InvalidRequestException("Transaction bundle contains multiple resources with ID: " + res.getId()); + } + } else if (res.getId().hasResourceType() && res.getId().hasIdPart()) { IdDt nextId = res.getId().toUnqualifiedVersionless(); if (!allIds.add(nextId)) { throw new InvalidRequestException("Transaction bundle contains multiple resources with ID: " + nextId); @@ -134,7 +138,7 @@ public class FhirSystemDaoDstu1 extends BaseFhirSystemDao> { } else { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionOperationWithMultipleMatchFailure", nextResouceOperationIn.name(), matchUrl, candidateMatches.size())); } - } else if (nextId.isEmpty()) { + } else if (nextId.isEmpty() || nextId.isLocal()) { entity = null; } else { entity = tryToLoadEntity(nextId); @@ -144,7 +148,7 @@ public class FhirSystemDaoDstu1 extends BaseFhirSystemDao> { if (entity == null) { nextResouceOperationOut = BundleEntryTransactionMethodEnum.POST; entity = toEntity(nextResource); - if (nextId.isEmpty() == false && nextId.getIdPart().startsWith("cid:")) { + if (nextId.isEmpty() == false && "cid:".equals(nextId.getBaseUrl())) { ourLog.debug("Resource in transaction has ID[{}], will replace with server assigned ID", nextId.getIdPart()); } else if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.POST) { if (nextId.isEmpty() == false) { @@ -209,11 +213,11 @@ public class FhirSystemDaoDstu1 extends BaseFhirSystemDao> { if (nextId.toUnqualifiedVersionless().equals(newId)) { ourLog.info("Transaction resource ID[{}] is being updated", newId); } else { - if (!nextId.getIdPart().startsWith("#")) { - nextId = new IdDt(resourceName, nextId.getIdPart()); + if (nextId.isLocal()) { +// nextId = new IdDt(resourceName, nextId.getIdPart()); ourLog.info("Transaction resource ID[{}] has been assigned new ID[{}]", nextId, newId); idConversions.put(nextId, newId); - idConversions.put(new IdDt(nextId.getIdPart()), newId); + idConversions.put(new IdDt(resourceName + "/" + nextId.getValue()), newId); } } } 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 40f785984eb..1d765b83ed6 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 @@ -151,7 +151,7 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao { if (res != null) { nextResourceId = res.getId(); - if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType()) { + if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !nextResourceId.isLocal()) { nextResourceId = new IdDt(toResourceName(res.getClass()), nextResourceId.getIdPart()); res.setId(nextResourceId); } @@ -159,7 +159,11 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao { /* * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness */ - if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) { + if (nextResourceId.isLocal()) { + if (!allIds.add(nextResourceId)) { + throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId)); + } + } else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) { IdDt nextId = nextResourceId.toUnqualifiedVersionless(); if (!allIds.add(nextId)) { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId)); @@ -173,6 +177,8 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionEntryHasInvalidVerb", nextEntry.getTransaction().getMethod())); } + String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null; + switch (verb) { case POST: { // CREATE @@ -182,7 +188,7 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao { DaoMethodOutcome outcome; Entry newEntry = response.addEntry(); outcome = resourceDao.create(res, nextEntry.getTransaction().getIfNoneExist(), false); - handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry); + handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry, resourceType); break; } case DELETE: { @@ -218,7 +224,7 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao { outcome = resourceDao.update(res, parts.getResourceType() + '?' + parts.getParams(), false); } - handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry); + handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry, resourceType); break; } case GET: { @@ -249,7 +255,8 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao { int configuredMax = 100; // this should probably be configurable or something if (bundle.size() > configuredMax) { - oo.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails("Search nested within transaction found more than " + configuredMax + " matches, but paging is not supported in nested transactions"); + oo.addIssue().setSeverity(IssueSeverityEnum.WARNING) + .setDetails("Search nested within transaction found more than " + configuredMax + " matches, but paging is not supported in nested transactions"); } List resourcesToAdd = bundle.getResources(0, Math.min(bundle.size(), configuredMax)); for (IBaseResource next : resourcesToAdd) { @@ -330,16 +337,18 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao { return url; } - private static void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, Entry newEntry) { + private static void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, + Entry newEntry, String theResourceType) { IdDt newId = outcome.getId().toUnqualifiedVersionless(); - IdDt resourceId = nextResourceId.toUnqualifiedVersionless(); + IdDt resourceId = nextResourceId.isLocal() ? nextResourceId : nextResourceId.toUnqualifiedVersionless(); if (newId.equals(resourceId) == false) { - /* - * The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified - * kind too just to be lenient. - */ idSubstitutions.put(resourceId, newId); - idSubstitutions.put(resourceId.withResourceType(null), newId); + if (resourceId.isLocal()) { + /* + * The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified kind too just to be lenient. + */ + idSubstitutions.put(new IdDt(theResourceType + '/' + resourceId.getValue()), newId); + } } idToPersistedOutcome.put(newId, outcome); if (outcome.getCreated().booleanValue()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java index 1a63c7b9363..519088cf4c0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java @@ -50,17 +50,17 @@ public class ResourceLink implements Serializable { private String mySourcePath; @ManyToOne(optional = false) - @JoinColumn(name = "SRC_RESOURCE_ID", referencedColumnName="RES_ID") + @JoinColumn(name = "SRC_RESOURCE_ID", referencedColumnName="RES_ID", nullable=false) private ResourceTable mySourceResource; - @Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false) + @Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable=false) private Long mySourceResourcePid; @ManyToOne(optional = false) - @JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName="RES_ID") + @JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName="RES_ID", nullable=false) private ResourceTable myTargetResource; - @Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false) + @Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false,nullable=false) private Long myTargetResourcePid; public ResourceLink() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java index 8d980424f5a..966e5ed933b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java @@ -154,7 +154,7 @@ public class JpaResourceProviderDstu2 extends BaseJpaResour } @Validate - public MethodOutcome validate(@ResourceParam T theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode, + public MethodOutcome validate(@ResourceParam T theResource, @IdParam IdDt theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile) { final OperationOutcome oo = new OperationOutcome(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java index 70ae9d2c44d..330f98ea13a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java @@ -1395,6 +1395,12 @@ public class FhirResourceDaoDstu2Test { assertEquals(1, result.size()); assertEquals(obsId01.getIdPart(), result.get(0).getId().getIdPart()); + result = toList(ourObservationDao.search(Observation.SP_PATIENT, new ReferenceParam(patientId01.getIdPart()))); + assertEquals(1, result.size()); + + result = toList(ourObservationDao.search(Observation.SP_PATIENT, new ReferenceParam(patientId01.getIdPart()))); + assertEquals(1, result.size()); + result = toList(ourObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "999999999999"))); assertEquals(0, result.size()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java index f80558849a4..cf4971e8a3d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java @@ -358,7 +358,10 @@ public class FhirSystemDaoDstu1Test { List response = ourSystemDao.transaction(res); - ourLog.info(ourFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(response.get(0))); + String encodeResourceToString = ourFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(response.get(0)); + ourLog.info(encodeResourceToString); + + assertThat(encodeResourceToString, not(containsString("smsp"))); } /** diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java index d467bd84964..5bd38e37d35 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java @@ -846,7 +846,7 @@ public class FhirSystemDaoDstu2Test { assertEquals(OperationOutcome.class, resp.getEntry().get(0).getResource().getClass()); OperationOutcome outcome = (OperationOutcome) resp.getEntry().get(0).getResource(); - assertThat(outcome.getIssue().get(1).getDetails(), containsString("Placeholder resource ID \"Patient/urn:oid:0.1.2.3\" was replaced with permanent ID \"Patient/")); + assertThat(outcome.getIssue().get(1).getDetails(), containsString("Placeholder resource ID \"urn:oid:0.1.2.3\" was replaced with permanent ID \"Patient/")); assertTrue(resp.getEntry().get(1).getTransactionResponse().getLocation(), new IdDt(resp.getEntry().get(1).getTransactionResponse().getLocation()).getIdPart().matches("^[0-9]+$")); assertTrue(resp.getEntry().get(2).getTransactionResponse().getLocation(), new IdDt(resp.getEntry().get(2).getTransactionResponse().getLocation()).getIdPart().matches("^[0-9]+$")); @@ -894,7 +894,7 @@ public class FhirSystemDaoDstu2Test { assertEquals(OperationOutcome.class, resp.getEntry().get(0).getResource().getClass()); OperationOutcome outcome = (OperationOutcome) resp.getEntry().get(0).getResource(); - assertThat(outcome.getIssue().get(1).getDetails(), containsString("Placeholder resource ID \"Patient/urn:oid:0.1.2.3\" was replaced with permanent ID \"Patient/")); + assertThat(outcome.getIssue().get(1).getDetails(), containsString("Placeholder resource ID \"urn:oid:0.1.2.3\" was replaced with permanent ID \"Patient/")); assertTrue(resp.getEntry().get(1).getTransactionResponse().getLocation(), new IdDt(resp.getEntry().get(1).getTransactionResponse().getLocation()).getIdPart().matches("^[0-9]+$")); assertTrue(resp.getEntry().get(2).getTransactionResponse().getLocation(), new IdDt(resp.getEntry().get(2).getTransactionResponse().getLocation()).getIdPart().matches("^[0-9]+$")); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java index b22a7a716c8..2c57a05eb53 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java @@ -82,6 +82,7 @@ import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.gclient.IQuery; +import ca.uhn.fhir.rest.gclient.IReadExecutable; import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.rest.gclient.TokenClientParam; import ca.uhn.fhir.rest.server.Constants; @@ -410,6 +411,34 @@ public class ResourceProviderDstu2Test { } + @Test + public void testBundleCreate() throws Exception { + IGenericClient client = ourClient; + + String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/document-father.json")); + IdDt id = client.create().resource(resBody).execute().getId(); + + ourLog.info("Created: {}", id); + + ca.uhn.fhir.model.dstu2.resource.Bundle bundle = client.read().resource(ca.uhn.fhir.model.dstu2.resource.Bundle.class).withId(id).execute(); + + ourLog.info(ourFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); + } + + @Test + public void testBundleCreateWithTypeTransaction() throws Exception { + IGenericClient client = ourClient; + + String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/document-father.json")); + resBody = resBody.replace("\"type\": \"document\"", "\"type\": \"transaction\""); + try { + client.create().resource(resBody).execute().getId(); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value other than 'document' - Value was: transaction")); + } + } + /** * See #147 */ diff --git a/hapi-fhir-jpaserver-base/src/test/resources/document-father.json b/hapi-fhir-jpaserver-base/src/test/resources/document-father.json new file mode 100644 index 00000000000..923bb6ea805 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/document-father.json @@ -0,0 +1,475 @@ +{ + "resourceType": "Bundle", + "meta": { + "lastUpdated": "2013-05-28T22:12:21Z", + "tag": [ + { + "system": "http://hl7.org/fhir/tag", + "code": "document" + } + ] + }, + "type": "document", + "base": "http://fhir.healthintersections.com.au/open", + "entry": [ + { + "base": "urn:uuid:", + "resource": { + "resourceType": "Composition", + "id": "180f219f-97a8-486d-99d9-ed631fe4fc57", + "meta": { + "lastUpdated": "2013-05-28T22:12:21Z" + }, + "text": { + "status": "generated", + "div": "

Generated Narrative with Details

id: 180f219f-97a8-486d-99d9-ed631fe4fc57

meta:

date: Feb 1, 2013 12:30:02 PM

type: Discharge Summary from Responsible Clinician (Details : {LOINC code '28655-9' = 'Physician attending Discharge summary)

status: final

confidentiality: N

author: Doctor Dave. Generated Summary: 23; Adam Careful

encounter: http://fhir.healthintersections.com.au/open/Encounter/doc-example

" + }, + "date": "2013-02-01T12:30:02Z", + "type": { + "coding": [ + { + "system": "http://loinc.org", + "code": "28655-9" + } + ], + "text": "Discharge Summary from Responsible Clinician" + }, + "status": "final", + "confidentiality": "N", + "subject": { + "reference": "http://fhir.healthintersections.com.au/open/Patient/d1", + "display": "Eve Everywoman" + }, + "author": [ + { + "reference": "Practitioner/example", + "display": "Doctor Dave" + } + ], + "encounter": { + "reference": "http://fhir.healthintersections.com.au/open/Encounter/doc-example" + }, + "section": [ + { + "title": "Reason for admission", + "content": { + "reference": "urn:uuid:d0dd51d3-3ab2-4c84-b697-a630c3e40e7a" + } + }, + { + "title": "Medications on Discharge", + "content": { + "reference": "urn:uuid:673f8db5-0ffd-4395-9657-6da00420bbc1" + } + }, + { + "title": "Known allergies", + "content": { + "reference": "urn:uuid:68f86194-e6e1-4f65-b64a-5314256f8d7b" + } + } + ] + } + }, + { + "resource": { + "resourceType": "Practitioner", + "id": "example", + "meta": { + "lastUpdated": "2013-05-05T16:13:03Z" + }, + "text": { + "status": "generated", + "div": "
\n \n

Dr Adam Careful is a Referring Practitioner for Acme Hospital from 1-Jan 2012 to 31-Mar\n 2012

\n \n
" + }, + "identifier": [ + { + "system": "http://www.acme.org/practitioners", + "value": "23" + } + ], + "name": { + "family": [ + "Careful" + ], + "given": [ + "Adam" + ], + "prefix": [ + "Dr" + ] + }, + "practitionerRole": [ + { + "managingOrganization": { + "reference": "Organization/1" + }, + "role": { + "coding": [ + { + "system": "http://hl7.org/fhir/v2/0286", + "code": "RP" + } + ] + }, + "period": { + "start": "2012-01-01", + "end": "2012-03-31" + } + } + ] + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "d1", + "text": { + "status": "generated", + "div": "
\n

Eve Everywoman

\n
" + }, + "name": [ + { + "text": "Eve Everywoman", + "family": [ + "Everywoman1" + ], + "given": [ + "Eve" + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "555-555-2003", + "use": "work" + } + ], + "gender": "female", + "birthDate": "1955-01-06", + "address": [ + { + "use": "home", + "line": [ + "2222 Home Street" + ] + } + ], + "active": true + } + }, + { + "resource": { + "resourceType": "Encounter", + "id": "doc-example", + "meta": { + "lastUpdated": "2013-05-05T16:13:03Z" + }, + "text": { + "status": "generated", + "div": "
Admitted to Orthopedics Service,\n Middlemore Hospital between Jan 20 and Feb ist 2013
" + }, + "identifier": [ + { + "value": "S100" + } + ], + "status": "finished", + "class": "inpatient", + "type": [ + { + "text": "Orthopedic Admission" + } + ], + "patient": { + "reference": "Patient/d1" + }, + "period": { + "start": "2013-01-20T12:30:02Z", + "end": "2013-02-01T12:30:02Z" + }, + "hospitalization": { + "dischargeDisposition": { + "text": "Discharged to care of GP" + } + } + } + }, + { + "base": "urn:uuid:", + "resource": { + "resourceType": "List", + "id": "d0dd51d3-3ab2-4c84-b697-a630c3e40e7a", + "meta": { + "lastUpdated": "2013-05-05T16:13:03Z" + }, + "text": { + "status": "additional", + "div": "
\n \n \n \n \n \n \n \n \n \n \n \n
Details\n
Acute Asthmatic attack. Was wheezing\n for days prior to admission.\n
\n
" + }, + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "8646-2", + "display": "Hospital admission diagnosis" + } + ] + }, + "subject": { + "reference": "http://fhir.healthintersections.com.au/open/Patient/d1", + "display": "Peter Patient" + }, + "status": "current", + "mode": "working", + "entry": [ + { + "item": { + "reference": "urn:uuid:541a72a8-df75-4484-ac89-ac4923f03b81" + } + } + ] + } + }, + { + "base": "urn:uuid:", + "resource": { + "resourceType": "Observation", + "id": "541a72a8-df75-4484-ac89-ac4923f03b81", + "meta": { + "lastUpdated": "2013-05-05T16:13:03Z" + }, + "text": { + "status": "additional", + "div": "
Acute Asthmatic attack. Was wheezing\n for days prior to admission.
" + }, + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "46241-6" + } + ], + "text": "Reason for admission" + }, + "valueString": "Acute Asthmatic attack. Was wheezing for days prior to admission.", + "status": "final", + "reliability": "ok" + } + }, + { + "base": "urn:uuid:", + "resource": { + "resourceType": "List", + "id": "673f8db5-0ffd-4395-9657-6da00420bbc1", + "meta": { + "lastUpdated": "2013-05-05T16:13:03Z" + }, + "text": { + "status": "additional", + "div": "
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
MedicationLast ChangeLast ChangeReason
Theophylline 200mg BD after mealscontinued
Ventolin InhalerstoppedGetting side effect of tremor
\n
" + }, + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "10183-2", + "display": "Hospital discharge medications" + } + ] + }, + "subject": { + "reference": "http://fhir.healthintersections.com.au/open/Patient/d1", + "display": "Peter Patient" + }, + "status": "current", + "mode": "working", + "entry": [ + { + "flag": [ + { + "coding": [ + { + "system": "http://www.ithealthboard.health.nz/fhir/ValueSet/medicationStatus", + "code": "started" + } + ] + } + ], + "item": { + "reference": "urn:uuid:124a6916-5d84-4b8c-b250-10cefb8e6e86" + } + }, + { + "flag": [ + { + "coding": [ + { + "system": "http://www.ithealthboard.health.nz/fhir/ValueSet/medicationStatus", + "code": "stopped" + } + ] + } + ], + "deleted": true, + "item": { + "reference": "MedicationPrescription/1", + "display": "use of Ventolin Inhaler was discontinued" + } + } + ] + } + }, + { + "base": "urn:uuid:", + "resource": { + "resourceType": "MedicationPrescription", + "id": "124a6916-5d84-4b8c-b250-10cefb8e6e86", + "meta": { + "lastUpdated": "2013-05-05T16:13:03Z" + }, + "text": { + "status": "generated", + "div": "
\n

Theophylline 200mg twice a day

\n
" + }, + "contained": [ + { + "resourceType": "Medication", + "id": "med1", + "name": "Theophylline 200mg", + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "66493003" + } + ] + } + } + ], + "patient": { + "reference": "http://fhir.healthintersections.com.au/open/Patient/d1", + "display": "Peter Patient" + }, + "prescriber": { + "reference": "Practitioner/example", + "display": "Peter Practitioner" + }, + "reasonCodeableConcept": { + "text": "Management of Asthma" + }, + "medicationReference": { + "reference": "#med1", + "display": "Theophylline 200mg BD" + }, + "dosageInstruction": [ + { + "additionalInstructions": { + "text": "Take with Food" + }, + "scheduledTiming": { + "repeat": { + "frequency": 2, + "period": 1, + "periodUnits": "d" + } + }, + "route": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "394899003", + "display": "oral administration of treatment" + } + ] + }, + "doseQuantity": { + "value": 1, + "units": "tablet", + "system": "http://unitsofmeasure.org", + "code": "tbl" + } + } + ] + } + }, + { + "base": "urn:uuid:", + "resource": { + "resourceType": "List", + "id": "68f86194-e6e1-4f65-b64a-5314256f8d7b", + "meta": { + "lastUpdated": "2013-05-05T16:13:03Z" + }, + "text": { + "status": "additional", + "div": "
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
AllergenReaction
DoxycyclineHives
\n
" + }, + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "48765-2", + "display": "Allergies and adverse reactions Document" + } + ] + }, + "subject": { + "reference": "http://fhir.healthintersections.com.au/open/Patient/d1", + "display": "Peter Patient" + }, + "status": "current", + "mode": "working", + "entry": [ + { + "item": { + "reference": "urn:uuid:47600e0f-b6b5-4308-84b5-5dec157f7637" + } + } + ] + } + }, + { + "base": "urn:uuid:", + "resource": { + "resourceType": "AllergyIntolerance", + "id": "47600e0f-b6b5-4308-84b5-5dec157f7637", + "meta": { + "lastUpdated": "2013-05-05T16:13:03Z" + }, + "text": { + "status": "generated", + "div": "
Sensitivity to Doxycycline :\n Hives
" + }, + "recordedDate": "2012-09-17", + "patient": { + "reference": "http://fhir.healthintersections.com.au/open/Patient/d1", + "display": "Eve Everywoman" + }, + "substance": { + "text": "Doxycycline" + }, + "status": "confirmed", + "criticality": "high", + "type": "immune", + "event": [ + { + "manifestation": [ + { + "coding": [ + { + "system": "http://example.org/system", + "code": "xxx", + "display": "Hives" + } + ], + "text": "Hives" + } + ] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/IdDtTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/IdDtTest.java index d0c2e9f363c..13578cf1cf3 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/IdDtTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/IdDtTest.java @@ -34,6 +34,22 @@ public class IdDtTest { assertFalse(id.isLocal()); } + @Test + public void testDetectLocalBase() { + assertEquals("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getValue()); + assertEquals("urn:uuid:", new IdDt("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl()); + assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart()); + + assertEquals("cid:180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getValue()); + assertEquals("cid:", new IdDt("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl()); + assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart()); + + assertEquals("#180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("#180f219f-97a8-486d-99d9-ed631fe4fc57").getValue()); + assertEquals("#", new IdDt("#180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl()); + assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("#180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart()); + } + + /** * See #67 */ @@ -41,34 +57,11 @@ public class IdDtTest { public void testComplicatedLocal() { IdDt id = new IdDt("#Patient/cid:Patient-72/_history/1"); assertTrue(id.isLocal()); - assertNull(id.getBaseUrl()); + assertEquals("#", id.getBaseUrl()); assertNull(id.getResourceType()); assertNull(id.getVersionIdPart()); - assertEquals("#Patient/cid:Patient-72/_history/1", id.getIdPart()); + assertEquals("Patient/cid:Patient-72/_history/1", id.getIdPart()); - IdDt id2 = new IdDt("#Patient/cid:Patient-72/_history/1"); - assertEquals(id, id2); - - id2 = id2.toUnqualified(); - assertTrue(id2.isLocal()); - assertNull(id2.getBaseUrl()); - assertNull(id2.getResourceType()); - assertNull(id2.getVersionIdPart()); - assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart()); - - id2 = id2.toVersionless(); - assertTrue(id2.isLocal()); - assertNull(id2.getBaseUrl()); - assertNull(id2.getResourceType()); - assertNull(id2.getVersionIdPart()); - assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart()); - - id2 = id2.toUnqualifiedVersionless(); - assertTrue(id2.isLocal()); - assertNull(id2.getBaseUrl()); - assertNull(id2.getResourceType()); - assertNull(id2.getVersionIdPart()); - assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart()); } @Test diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java index 588a5f1891b..a67059ccc58 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java @@ -99,7 +99,7 @@ public class JsonParserTest { assertEquals(exp, act); } - + @Test public void testDecimalPrecisionPreserved() { String number = "52.3779939997090374535378485873776474764643249869328698436986235758587"; diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java index ca2a8241465..738facb25c4 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java @@ -537,6 +537,8 @@ public class XmlParserTest { // Re-parse the bundle patient = (Patient) xmlParser.parseResource(xmlParser.encodeResourceToString(patient)); assertEquals("#1", patient.getManagingOrganization().getReference().getValue()); + assertEquals("#", patient.getManagingOrganization().getReference().getBaseUrl()); + assertEquals("1", patient.getManagingOrganization().getReference().getIdPart()); assertNotNull(patient.getManagingOrganization().getResource()); org = (Organization) patient.getManagingOrganization().getResource(); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java index dbdfcc3e82b..b591f0c7b09 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java @@ -1,7 +1,13 @@ package ca.uhn.fhir.parser; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; import java.io.IOException; import java.util.ArrayList; @@ -14,7 +20,6 @@ import net.sf.json.JsonConfig; import org.apache.commons.io.IOUtils; import org.hamcrest.Matchers; -import org.hamcrest.core.StringContains; import org.junit.Assert; import org.junit.Test; @@ -64,6 +69,87 @@ public class JsonParserDstu2Test { assertThat(ourCtx.newJsonParser().setOmitResourceId(true).encodeResourceToString(p), not(containsString("123"))); } + @Test + public void testParseAndEncodeBundleWithUuidBase() { + //@formatter:off + String input = + "{\n" + + " \"resourceType\":\"Bundle\",\n" + + " \"type\":\"document\",\n" + + " \"entry\":[\n" + + " {\n" + + " \"base\":\"urn:uuid:\",\n" + + " \"resource\":{\n" + + " \"resourceType\":\"Composition\",\n" + + " \"id\":\"180f219f-97a8-486d-99d9-ed631fe4fc57\",\n" + + " \"meta\":{\n" + + " \"lastUpdated\":\"2013-05-28T22:12:21Z\"\n" + + " },\n" + + " \"text\":{\n" + + " \"status\":\"generated\",\n" + + " \"div\":\"

Generated Narrative with Details

id: 180f219f-97a8-486d-99d9-ed631fe4fc57

meta:

date: Feb 1, 2013 12:30:02 PM

type: Discharge Summary from Responsible Clinician (Details : {LOINC code '28655-9' = 'Physician attending Discharge summary)

status: final

confidentiality: N

author: Doctor Dave. Generated Summary: 23; Adam Careful

encounter: http://fhir.healthintersections.com.au/open/Encounter/doc-example

\"\n" + + " },\n" + + " \"date\":\"2013-02-01T12:30:02Z\",\n" + + " \"type\":{\n" + + " \"coding\":[\n" + + " {\n" + + " \"system\":\"http://loinc.org\",\n" + + " \"code\":\"28655-9\"\n" + + " }\n" + + " ],\n" + + " \"text\":\"Discharge Summary from Responsible Clinician\"\n" + + " },\n" + + " \"status\":\"final\",\n" + + " \"confidentiality\":\"N\",\n" + + " \"subject\":{\n" + + " \"reference\":\"http://fhir.healthintersections.com.au/open/Patient/d1\",\n" + + " \"display\":\"Eve Everywoman\"\n" + + " },\n" + + " \"author\":[\n" + + " {\n" + + " \"reference\":\"Practitioner/example\",\n" + + " \"display\":\"Doctor Dave\"\n" + + " }\n" + + " ],\n" + + " \"encounter\":{\n" + + " \"reference\":\"http://fhir.healthintersections.com.au/open/Encounter/doc-example\"\n" + + " },\n" + + " \"section\":[\n" + + " {\n" + + " \"title\":\"Reason for admission\",\n" + + " \"content\":{\n" + + " \"reference\":\"urn:uuid:d0dd51d3-3ab2-4c84-b697-a630c3e40e7a\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"title\":\"Medications on Discharge\",\n" + + " \"content\":{\n" + + " \"reference\":\"urn:uuid:673f8db5-0ffd-4395-9657-6da00420bbc1\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"title\":\"Known allergies\",\n" + + " \"content\":{\n" + + " \"reference\":\"urn:uuid:68f86194-e6e1-4f65-b64a-5314256f8d7b\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " }" + + " ]" + + "}"; + //@formatter:on + + ca.uhn.fhir.model.dstu2.resource.Bundle parsed = ourCtx.newJsonParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, input); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); + ourLog.info(encoded); + + assertEquals("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57", parsed.getEntry().get(0).getResource().getId().getValue()); + assertEquals("urn:uuid:", parsed.getEntry().get(0).getResource().getId().getBaseUrl()); + assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", parsed.getEntry().get(0).getResource().getId().getIdPart()); + assertThat(encoded, containsString("\"id\":\"180f219f-97a8-486d-99d9-ed631fe4fc57\"")); + } @Test diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java index 5f8059c9da3..e9138ae73e6 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java @@ -1108,7 +1108,15 @@ public class GenericClientDstu2Test { assertNotNull(response.getOperationOutcome()); assertEquals("FOOBAR", response.getOperationOutcome().getIssueFirstRep().getDetailsElement().getValue()); idx++; -} + + response = client.validate().resource(ourCtx.newJsonParser().encodeResourceToString(p)).prettyPrint().execute(); + assertEquals("http://example.com/fhir/Patient/$validate?_format=json&_pretty=true", capt.getAllValues().get(idx).getURI().toASCIIString()); + assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); + assertThat(extractBody(capt, idx), containsString("\"resourceType\":\"Parameters\",\n")); + assertNotNull(response.getOperationOutcome()); + assertEquals("FOOBAR", response.getOperationOutcome().getIssueFirstRep().getDetailsElement().getValue()); + idx++; + } @BeforeClass public static void beforeClass() { diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/IdType.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/IdType.java index 780b289e102..4520f4d5dc2 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/IdType.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/IdType.java @@ -29,7 +29,8 @@ package org.hl7.fhir.instance.model; */ -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.math.BigDecimal; @@ -42,6 +43,8 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import ca.uhn.fhir.parser.DataFormatException; + /** * This class represents the logical identity for a resource, or as much of that * identity is known. In FHIR, every resource must have a "logical ID" which @@ -311,6 +314,11 @@ public final class IdType extends UriType implements IPrimitiveType, IId public String getValue() { String retVal = super.getValue(); if (retVal == null && myHaveComponentParts) { + + if (determineLocalPrefix(myBaseUrl) != null && myResourceType == null && myUnqualifiedVersionId == null) { + return myBaseUrl + myUnqualifiedId; + } + StringBuilder b = new StringBuilder(); if (isNotBlank(myBaseUrl)) { b.append(myBaseUrl); @@ -428,9 +436,37 @@ public final class IdType extends UriType implements IPrimitiveType, IId */ @Override public boolean isLocal() { - return myUnqualifiedId != null && myUnqualifiedId.isEmpty() == false && myUnqualifiedId.charAt(0) == '#'; + return "#".equals(myBaseUrl); } + private String determineLocalPrefix(String theValue) { + if (theValue == null || theValue.isEmpty()) { + return null; + } + if (theValue.startsWith("#")) { + return "#"; + } + int lastPrefix = -1; + for (int i = 0; i < theValue.length(); i++) { + char nextChar = theValue.charAt(i); + if (nextChar == ':') { + lastPrefix = i; + } else if (!Character.isLetter(nextChar) || !Character.isLowerCase(nextChar)) { + break; + } + } + if (lastPrefix != -1) { + String candidate = theValue.substring(0, lastPrefix + 1); + if (candidate.startsWith("cid:") || candidate.startsWith("urn:")) { + return candidate; + } else { + return null; + } + } else { + return null; + } + } + /** * Set the value * @@ -443,22 +479,29 @@ public final class IdType extends UriType implements IPrimitiveType, IId *

*/ @Override - public IdType setValue(String theValue) { + public IdType setValue(String theValue) throws DataFormatException { // TODO: add validation super.setValue(theValue); myHaveComponentParts = false; + + String localPrefix = determineLocalPrefix(theValue); + if (StringUtils.isBlank(theValue)) { myBaseUrl = null; super.setValue(null); myUnqualifiedId = null; myUnqualifiedVersionId = null; myResourceType = null; - } else if (theValue.charAt(0) == '#') { + } else if (theValue.charAt(0) == '#' && theValue.length() > 1) { super.setValue(theValue); - myUnqualifiedId = theValue; + myBaseUrl = "#"; + myUnqualifiedId = theValue.substring(1); myUnqualifiedVersionId = null; myResourceType = null; myHaveComponentParts = true; + } else if (localPrefix != null) { + myBaseUrl = localPrefix; + myUnqualifiedId = theValue.substring(localPrefix.length()); } else { int vidIndex = theValue.indexOf("/_history/"); int idIndex; diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/model/IdTypeTest.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/model/IdTypeTest.java new file mode 100644 index 00000000000..7312c7bef1f --- /dev/null +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/model/IdTypeTest.java @@ -0,0 +1,226 @@ +package ca.uhn.fhir.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.math.BigDecimal; + +import org.hl7.fhir.instance.model.IdType; +import org.hl7.fhir.instance.model.Patient; +import org.hl7.fhir.instance.model.Reference; +import org.junit.BeforeClass; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; + +public class IdTypeTest { + + private static FhirContext ourCtx; + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(IdTypeTest.class); + + @Test + public void testDetectLocal() { + IdType id; + + id = new IdType("#123"); + assertEquals("#123", id.getValue()); + assertTrue(id.isLocal()); + + id = new IdType("#Medication/499059CE-CDD4-48BC-9014-528A35D15CED/_history/1"); + assertEquals("#Medication/499059CE-CDD4-48BC-9014-528A35D15CED/_history/1", id.getValue()); + assertTrue(id.isLocal()); + + id = new IdType("http://example.com/Patient/33#123"); + assertEquals("http://example.com/Patient/33#123", id.getValue()); + assertFalse(id.isLocal()); + } + + @Test + public void testDetectLocalBase() { + assertEquals("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getValue()); + assertEquals("urn:uuid:", new IdType("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl()); + assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart()); + + assertEquals("cid:180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getValue()); + assertEquals("cid:", new IdType("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl()); + assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart()); + + assertEquals("#180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getValue()); + assertEquals("#", new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl()); + assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart()); + } + + + /** + * See #67 + */ + @Test + public void testComplicatedLocal() { + IdType id = new IdType("#Patient/cid:Patient-72/_history/1"); + assertTrue(id.isLocal()); + assertEquals("#", id.getBaseUrl()); + assertNull(id.getResourceType()); + assertNull(id.getVersionIdPart()); + assertEquals("Patient/cid:Patient-72/_history/1", id.getIdPart()); + + IdType id2 = new IdType("#Patient/cid:Patient-72/_history/1"); + assertEquals(id, id2); + + id2 = id2.toUnqualified(); + assertFalse(id2.isLocal()); + assertNull(id2.getBaseUrl()); + assertNull(id2.getResourceType()); + assertNull(id2.getVersionIdPart()); + assertEquals("Patient/cid:Patient-72/_history/1", id2.getIdPart()); + + } + + @Test + public void testDetermineBase() { + + IdType rr; + + rr = new IdType("http://foo/fhir/Organization/123"); + assertEquals("http://foo/fhir", rr.getBaseUrl()); + + rr = new IdType("http://foo/fhir/Organization/123/_history/123"); + assertEquals("http://foo/fhir", rr.getBaseUrl()); + + rr = new IdType("Organization/123/_history/123"); + assertEquals(null, rr.getBaseUrl()); + + } + + @Test + public void testParseValueAbsolute() { + Patient patient = new Patient(); + IdType rr = new IdType(); + rr.setValue("http://foo/fhir/Organization/123"); + + patient.setManagingOrganization(new Reference(rr)); + + Patient actual = parseAndEncode(patient); + Reference ref = actual.getManagingOrganization(); + assertEquals("Organization", ref.getReferenceElement().getResourceType()); + assertEquals("123", ref.getReferenceElement().getIdPart()); + + } + + @Test + public void testBigDecimalIds() { + + IdType id = new IdType(new BigDecimal("123")); + assertEquals(id.getIdPartAsBigDecimal(), new BigDecimal("123")); + + } + + @Test + public void testParseValueAbsoluteWithVersion() { + Patient patient = new Patient(); + IdType rr = new IdType(); + rr.setValue("http://foo/fhir/Organization/123/_history/999"); + patient.setManagingOrganization(new Reference(rr)); + + Patient actual = parseAndEncode(patient); + Reference ref = actual.getManagingOrganization(); + assertEquals("Organization", ref.getReferenceElement().getResourceType()); + assertEquals("123", ref.getReferenceElement().getIdPart()); + assertEquals(null, ref.getReferenceElement().getVersionIdPart()); + + } + + + @Test + public void testViewMethods() { + IdType i = new IdType("http://foo/fhir/Organization/123/_history/999"); + assertEquals("Organization/123/_history/999", i.toUnqualified().getValue()); + assertEquals("http://foo/fhir/Organization/123", i.toVersionless().getValue()); + assertEquals("Organization/123", i.toUnqualifiedVersionless().getValue()); + } + + @Test + public void testParseValueWithVersion() { + Patient patient = new Patient(); + IdType rr = new IdType(); + rr.setValue("/123/_history/999"); + patient.setManagingOrganization(new Reference(rr)); + + Patient actual = parseAndEncode(patient); + Reference ref = actual.getManagingOrganization(); + assertEquals(null, ref.getReferenceElement().getResourceType()); + assertEquals("123", ref.getReferenceElement().getIdPart()); + assertEquals(null, ref.getReferenceElement().getVersionIdPart()); + + } + + @Test + public void testParseValueMissingType1() { + Patient patient = new Patient(); + IdType rr = new IdType(); + rr.setValue("/123"); + patient.setManagingOrganization(new Reference(rr)); + + Patient actual = parseAndEncode(patient); + Reference ref = actual.getManagingOrganization(); + assertEquals(null, ref.getReferenceElement().getResourceType()); + assertEquals("123", ref.getReferenceElement().getIdPart()); + + } + + @Test + public void testParseValueMissingType2() { + Patient patient = new Patient(); + IdType rr = new IdType(); + rr.setValue("123"); + patient.setManagingOrganization(new Reference(rr)); + + Patient actual = parseAndEncode(patient); + Reference ref = actual.getManagingOrganization(); + assertEquals(null, ref.getReferenceElement().getResourceType()); + assertEquals("123", ref.getReferenceElement().getIdPart()); + + } + + @Test + public void testParseValueRelative1() { + Patient patient = new Patient(); + IdType rr = new IdType(); + rr.setValue("Organization/123"); + patient.setManagingOrganization(new Reference(rr)); + + Patient actual = parseAndEncode(patient); + Reference ref = actual.getManagingOrganization(); + assertEquals("Organization", ref.getReferenceElement().getResourceType()); + assertEquals("123", ref.getReferenceElement().getIdPart()); + + } + + @Test + public void testParseValueRelative2() { + Patient patient = new Patient(); + IdType rr = new IdType(); + rr.setValue("/Organization/123"); + patient.setManagingOrganization(new Reference(rr)); + + Patient actual = parseAndEncode(patient); + Reference ref = actual.getManagingOrganization(); + assertEquals("Organization", ref.getReferenceElement().getResourceType()); + assertEquals("123", ref.getReferenceElement().getIdPart()); + + } + + private Patient parseAndEncode(Patient patient) { + String encoded = ourCtx.newXmlParser().encodeResourceToString(patient); + ourLog.info("\n" + encoded); + return ourCtx.newXmlParser().parseResource(Patient.class, encoded); + } + + @BeforeClass + public static void beforeClass() { + ourCtx = new FhirContext(); + } + +} diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java index e2d34e880e2..17092aac9df 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java @@ -597,6 +597,7 @@ public class Controller { CaptureInterceptor interceptor = new CaptureInterceptor(); GenericClient client = theRequest.newClient(theReq, getContext(theRequest), myConfig, interceptor); + client.setPrettyPrint(true); Class type = null; // def.getImplementingClass(); if ("history-type".equals(theMethod)) { @@ -616,8 +617,10 @@ public class Controller { try { if (body.startsWith("{")) { resource = getContext(theRequest).newJsonParser().parseResource(type, body); + client.setEncoding(EncodingEnum.JSON); } else if (body.startsWith("<")) { resource = getContext(theRequest).newXmlParser().parseResource(type, body); + client.setEncoding(EncodingEnum.XML); } else { theModel.put("errorMsg", "Message body does not appear to be a valid FHIR resource instance document. Body should start with '<' (for XML encoding) or '{' (for JSON encoding)."); return; @@ -637,7 +640,7 @@ public class Controller { try { if (validate) { outcomeDescription = "Validate Resource"; - client.validate(resource); + client.validate().resource(resource).prettyPrint().execute(); } else { String id = theReq.getParameter("resource-create-id"); if ("update".equals(theMethod)) { diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java index 2d0f8a22f81..f99711471b7 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java @@ -92,9 +92,6 @@ public class TinderJpaRestServerMojo extends AbstractMojo { } for (String next : keys) { if (next.startsWith("resource.")) { - if (next.endsWith(".Bundle")) { - continue; - } baseResourceNames.add(next.substring("resource.".length()).toLowerCase()); } } diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm index 3096e10d358..300e95c0a8d 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm @@ -7,7 +7,8 @@ import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.jpa.provider.JpaResourceProvider${versionCapitalized}; import ca.uhn.fhir.jpa.dao.SearchParameterMap; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.annotation.*; import ca.uhn.fhir.model.${version}.composite.*; import ca.uhn.fhir.model.${version}.resource.*; @@ -16,7 +17,7 @@ import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.model.dstu.resource.Binary; // import ca.uhn.fhir.model.dstu2.resource.Bundle; -import ca.uhn.fhir.model.api.Bundle; +// import ca.uhn.fhir.model.api.Bundle; public class ${className}ResourceProvider extends JpaResourceProvider${versionCapitalized}<${className}> { diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm index 9ed8d416adc..91d1dcaa381 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm @@ -27,7 +27,12 @@ #foreach ( $res in $resources ) - + +#else + class="ca.uhn.fhir.jpa.dao.FhirResourceDao${versionCapitalized}"> +#end diff --git a/restful-server-example/pom.xml b/restful-server-example/pom.xml index ff9f7a68de0..8e269dc232d 100644 --- a/restful-server-example/pom.xml +++ b/restful-server-example/pom.xml @@ -14,7 +14,7 @@ ca.uhn.hapi.example restful-server-example - 1.0 + 1.0-SNAPSHOT war HAPI FHIR Sample RESTful Server