diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java index 5b55ef34f98..c5f8f2ed557 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java @@ -62,17 +62,12 @@ import ca.uhn.fhir.util.UrlUtil; @SuppressWarnings("deprecation") public class MethodUtil { - /** Non instantiable */ - private MethodUtil() { - // nothing - } - private static final String LABEL = "label=\""; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class); - private static final String SCHEME = "scheme=\""; private static final Set ourServletRequestTypes = new HashSet(); private static final Set ourServletResponseTypes = new HashSet(); - + private static final String SCHEME = "scheme=\""; static { ourServletRequestTypes.add("javax.servlet.ServletRequest"); ourServletResponseTypes.add("javax.servlet.ServletResponse"); @@ -80,6 +75,11 @@ public class MethodUtil { ourServletResponseTypes.add("javax.servlet.http.HttpServletResponse"); } + /** Non instantiable */ + private MethodUtil() { + // nothing + } + static void addTagsToPostOrPut(FhirContext theContext, IBaseResource resource, BaseHttpClientInvocation retVal) { if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { @@ -603,8 +603,17 @@ public class MethodUtil { * This is a utility method intended provided to help the JPA module. */ public static IQueryParameterAnd parseQueryParams(RuntimeSearchParam theParamDef, String theUnqualifiedParamName, List theParameters) { + RestSearchParameterTypeEnum paramType = theParamDef.getParamType(); + return parseQueryParams(paramType, theUnqualifiedParamName, theParameters); + } + + + /** + * This is a utility method intended provided to help the JPA module. + */ + public static IQueryParameterAnd parseQueryParams(RestSearchParameterTypeEnum paramType, String theUnqualifiedParamName, List theParameters) { QueryParameterAndBinder binder = null; - switch (theParamDef.getParamType()) { + switch (paramType) { case COMPOSITE: throw new UnsupportedOperationException(); case DATE: @@ -628,6 +637,9 @@ public class MethodUtil { case URI: binder = new QueryParameterAndBinder(UriAndListParam.class, Collections.> emptyList()); break; + case HAS: + binder = new QueryParameterAndBinder(HasAndListParam.class, Collections.> emptyList()); + break; } return binder.parse(theUnqualifiedParamName, theParameters); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/HasParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/HasParam.java index 40c68d41324..72ec082a402 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/HasParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/HasParam.java @@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; /** @@ -64,7 +65,7 @@ public class HasParam extends BaseParam implements IQueryParameterType { void doSetValueAsQueryToken(String theQualifier, String theValue) { String qualifier = defaultString(theQualifier); if (!qualifier.startsWith(":")) { - throwInvalidSyntaxException("_has" + qualifier); + throwInvalidSyntaxException(Constants.PARAM_HAS + qualifier); } int colonIndex0 = qualifier.indexOf(':', 1); validateColon(qualifier, colonIndex0); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java index 69927cae815..22a17a003ab 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java @@ -115,6 +115,7 @@ public class Constants { public static final String PARAM_DELETE = "_delete"; public static final String PARAM_ELEMENTS = "_elements"; public static final String PARAM_FORMAT = "_format"; + public static final String PARAM_HAS = "_has"; public static final String PARAM_HISTORY = "_history"; public static final String PARAM_INCLUDE = "_include"; public static final String PARAM_INCLUDE_QUALIFIER_RECURSE = ":recurse"; @@ -157,8 +158,8 @@ public class Constants { public static final int STATUS_HTTP_409_CONFLICT = 409; public static final int STATUS_HTTP_410_GONE = 410; public static final int STATUS_HTTP_412_PRECONDITION_FAILED = 412; - public static final int STATUS_HTTP_422_UNPROCESSABLE_ENTITY = 422; + public static final int STATUS_HTTP_422_UNPROCESSABLE_ENTITY = 422; public static final int STATUS_HTTP_500_INTERNAL_ERROR = 500; public static final int STATUS_HTTP_501_NOT_IMPLEMENTED = 501; public static final String TAG_SUBSETTED_CODE = "SUBSETTED"; 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 d5cc9537047..3ee157f83f3 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 @@ -130,13 +130,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.MethodUtil; import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.StringAndListParam; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.param.TokenAndListParam; -import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.param.UriAndListParam; -import ca.uhn.fhir.rest.param.UriParam; +import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -1467,14 +1461,23 @@ public abstract class BaseHapiFhirDao implements IDao { } IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); + String firstMsg = null; for (DeleteConflict next : theDeleteConflicts) { - String msg = "Unable to delete " + next.getTargetId().toUnqualifiedVersionless().getValue() - + " because at least one resource has a reference to this resource. First reference found was resource " + next.getTargetId().toUnqualifiedVersionless().getValue() + " in path " - + next.getSourcePath(); + StringBuilder b = new StringBuilder(); + b.append("Unable to delete "); + b.append(next.getTargetId().toUnqualifiedVersionless().getValue()); + b.append(" because at least one resource has a reference to this resource. First reference found was resource "); + b.append(next.getTargetId().toUnqualifiedVersionless().getValue()); + b.append(" in path "); + b.append(next.getSourcePath()); + String msg = b.toString(); + if (firstMsg == null) { + firstMsg = msg; + } OperationOutcomeUtil.addIssue(getContext(), oo, OO_SEVERITY_ERROR, msg, null, "processing"); } - throw new ResourceVersionConflictException("Delete failed because of constraint failure", oo); + throw new ResourceVersionConflictException(firstMsg, oo); } /** @@ -1694,6 +1697,12 @@ public abstract class BaseHapiFhirDao implements IDao { continue; } + if (Constants.PARAM_HAS.equals(nextParamName)) { + IQueryParameterAnd param = MethodUtil.parseQueryParams(RestSearchParameterTypeEnum.HAS, nextParamName, paramList); + paramMap.add(nextParamName, param); + continue; + } + if (Constants.PARAM_COUNT.equals(nextParamName)) { if (paramList.size() > 0 && paramList.get(0).size() > 0) { String intString = paramList.get(0).get(0); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index cc5735713d3..2b2701a14a3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -294,7 +294,6 @@ public abstract class BaseHapiFhirResourceDao extends B List deletedResources = deleteByUrl(theUrl, deleteConflicts, theRequestDetails); validateDeleteConflictsEmptyOrThrowException(deleteConflicts); - IBaseOperationOutcome oo; if (deletedResources.isEmpty()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index da7d76ee4e1..088ebba4085 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -1917,7 +1917,7 @@ public class SearchBuilder { addPredicateLanguage(nextParamEntry.getValue()); - } else if (nextParamName.equals("_has")) { + } else if (nextParamName.equals(Constants.PARAM_HAS)) { addPredicateHas(nextParamEntry.getValue(), theLastUpdated); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java index 8c5e79bbb05..8772b08903b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; * 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 + * 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, @@ -23,39 +23,19 @@ import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import javax.persistence.TypedQuery; import org.apache.http.NameValuePair; -import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryResponseComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.Meta; -import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; -import org.hl7.fhir.dstu3.model.Resource; -import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBaseReference; -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 org.hl7.fhir.instance.model.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; @@ -182,7 +162,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { } /** - * This method is called for nested bundles (e.g. if we received a transaction with an entry that + * This method is called for nested bundles (e.g. if we received a transaction with an entry that * was a GET search, this method is called on the bundle for the search result, that will be placed in the * outer bundle). This method applies the _summary and _content parameters to the output of * that bundle. @@ -217,7 +197,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { return retVal; } - + protected Meta toMeta(Collection tagDefinitions) { Meta retVal = new Meta(); for (TagDefinition next : tagDefinitions) { @@ -236,7 +216,6 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { return retVal; } - private ca.uhn.fhir.jpa.dao.IFhirResourceDao toDao(UrlParts theParts, String theVerb, String theUrl) { RuntimeResourceDefinition resType; try { @@ -269,7 +248,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null); notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails); } - + String actionName = "Transaction"; return transaction((ServletRequestDetails) theRequestDetails, theRequest, actionName); } @@ -307,13 +286,13 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionEntryHasInvalidVerb", nextReqEntry.getRequest().getMethod(), i)); } } - + /* * We want to execute the transaction request bundle elements in the order - * specified by the FHIR specification (see TransactionSorter) so we save the + * specified by the FHIR specification (see TransactionSorter) so we save the * original order in the request, then sort it. * - * Entries with a type of GET are removed from the bundle so that they + * Entries with a type of GET are removed from the bundle so that they * can be processed at the very end. We do this because the incoming resources * are saved in a two-phase way in order to deal with interdependencies, and * we want the GET processing to use the final indexing state @@ -329,12 +308,12 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { } } Collections.sort(theRequest.getEntry(), new TransactionSorter()); - + Set deletedResources = new HashSet(); List deleteConflicts = new ArrayList(); Map entriesToProcess = new IdentityHashMap(); Set nonUpdatedEntities = new HashSet(); - + /* * Loop through the request and process any entries of type * PUT, POST or DELETE @@ -436,7 +415,6 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { @SuppressWarnings("rawtypes") IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); - String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); DaoMethodOutcome outcome; @@ -459,11 +437,11 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { /* * Make sure that there are no conflicts from deletions. E.g. we can't delete something * if something else has a reference to it.. Unless the thing that has a reference to it - * was also deleted as a part of this transaction, which is why we check this now at the + * was also deleted as a part of this transaction, which is why we check this now at the * end. */ - - for (Iterator iter = deleteConflicts.iterator(); iter.hasNext(); ) { + + for (Iterator iter = deleteConflicts.iterator(); iter.hasNext();) { DeleteConflict next = iter.next(); if (deletedResources.contains(next.getTargetId().toUnqualifiedVersionless().getValue())) { iter.remove(); @@ -547,9 +525,9 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { requestDetails.setServletRequest(theRequestDetails.getServletRequest()); requestDetails.setRequestType(RequestTypeEnum.GET); requestDetails.setServer(theRequestDetails.getServer()); - + String url = extractTransactionUrlOrThrowException(nextReqEntry, HTTPVerb.GET); - + int qIndex = url.indexOf('?'); ArrayListMultimap paramValues = ArrayListMultimap.create(); requestDetails.setParameters(new HashMap()); @@ -574,7 +552,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { if (method == null) { throw new IllegalArgumentException("Unable to handle GET " + url); } - + if (isNotBlank(nextReqEntry.getRequest().getIfMatch())) { requestDetails.addHeader(Constants.HEADER_IF_MATCH, nextReqEntry.getRequest().getIfMatch()); } @@ -584,7 +562,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { if (isNotBlank(nextReqEntry.getRequest().getIfNoneMatch())) { requestDetails.addHeader(Constants.HEADER_IF_NONE_MATCH, nextReqEntry.getRequest().getIfNoneMatch()); } - + if (method instanceof BaseResourceReturningMethodBinding) { try { ResourceOrDstu1Bundle responseData = ((BaseResourceReturningMethodBinding) method).doInvokeServer(theRequestDetails.getServer(), requestDetails); @@ -602,12 +580,12 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { } } - + for (Entry nextEntry : entriesToProcess.entrySet()) { nextEntry.getKey().getResponse().setLocation(nextEntry.getValue().getIdDt().toUnqualified().getValue()); nextEntry.getKey().getResponse().setEtag(nextEntry.getValue().getIdDt().getVersionIdPart()); } - + long delay = System.currentTimeMillis() - start; ourLog.info(theActionName + " completed in {}ms", new Object[] { delay }); @@ -634,7 +612,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { } else { newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK)); } - newEntry.getResponse().setLastModified(((Resource)theRes).getMeta().getLastUpdated()); + newEntry.getResponse().setLastModified(((Resource) theRes).getMeta().getLastUpdated()); } private static boolean isPlaceholder(IdType theId) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java index bdd01237434..d5e1b95d32b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java @@ -8,6 +8,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.matchesPattern; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -61,6 +62,7 @@ import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.Meta; import org.hl7.fhir.dstu3.model.NamingSystem; import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.OperationDefinition; import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; @@ -81,6 +83,7 @@ import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.valuesets.ObservationCategory; import org.junit.AfterClass; import org.junit.Ignore; import org.junit.Test; @@ -143,6 +146,35 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { } } + @Test + public void testDeleteWithHas() { + Observation obs1 = new Observation(); + obs1.setStatus(ObservationStatus.FINAL); + IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.setStatus(ObservationStatus.FINAL); + IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless(); + + DiagnosticReport rpt = new DiagnosticReport(); + rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER"); + rpt.addResult(new Reference(obs2id)); + IIdType rptId = myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless(); + + myObservationDao.read(obs1id); + myObservationDao.read(obs2id); + + try { + myObservationDao.deleteByUrl("Observation?_has:DiagnosticReport:result:identifier=foo|IDENTIFIER", mySrd); + fail(); + } catch (ResourceVersionConflictException e) { + assertThat(e.getMessage(), matchesPattern("Unable to delete Observation/[0-9]+ because at least one resource has a reference to this resource. First reference found was resource Observation/[0-9]+ in path DiagnosticReport.result")); + } + + myObservationDao.read(obs1id); + myObservationDao.read(obs2id); + } + @Test public void testCodeSystemCreateAndDelete() { CodeSystem cs = new CodeSystem(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java index 9471b91991b..d037e7fe519 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.greaterThan; @@ -27,25 +28,14 @@ import java.util.List; import javax.mail.Quota.Resource; import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.model.Appointment; -import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryResponseComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.Medication; -import org.hl7.fhir.dstu3.model.MedicationOrder; -import org.hl7.fhir.dstu3.model.Meta; -import org.hl7.fhir.dstu3.model.Observation; -import org.hl7.fhir.dstu3.model.OperationOutcome; +import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.dstu3.model.Reference; -import org.hl7.fhir.dstu3.model.UriType; -import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; @@ -68,10 +58,7 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.util.TestUtil; @@ -79,11 +66,28 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu3Test.class); - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); + @After + public void after() { + myDaoConfig.setAllowInlineMatchUrlReferences(false); } + @SuppressWarnings("unchecked") + private T find(Bundle theBundle, Class theType, int theIndex) { + int count = 0; + for (BundleEntryComponent nextEntry : theBundle.getEntry()) { + if (nextEntry.getResource() != null && theType.isAssignableFrom(nextEntry.getResource().getClass())) { + if (count == theIndex) { + T t = (T) nextEntry.getResource(); + return t; + } + count++; + } + } + fail(); + return null; + } + + /** * See #410 */ @@ -101,141 +105,43 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } @Test - public void testTransactionDoesNotAllowDanglingTemporaryIds() throws Exception { - String input = IOUtils.toString(getClass().getResourceAsStream("/cdr-bundle.json"), StandardCharsets.UTF_8); - Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + public void testDeleteWithHas() { + Observation obs1 = new Observation(); + obs1.setStatus(ObservationStatus.FINAL); + IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless(); - BundleEntryComponent entry = bundle.addEntry(); - Patient p = new Patient(); - p.getManagingOrganization().setReference("urn:uuid:30ce60cf-f7cb-4196-961f-cadafa8b7ff5"); - entry.setResource(p); - entry.getRequest().setMethod(HTTPVerb.POST); - entry.getRequest().setUrl("Patient"); + Observation obs2 = new Observation(); + obs2.setStatus(ObservationStatus.FINAL); + IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless(); + + DiagnosticReport rpt = new DiagnosticReport(); + rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER"); + rpt.addResult(new Reference(obs2id)); + IIdType rptId = myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless(); + myObservationDao.read(obs1id); + myObservationDao.read(obs2id); + + rpt = new DiagnosticReport(); + rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER"); + + Bundle b = new Bundle(); + b.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Observation?_has:DiagnosticReport:result:identifier=foo|IDENTIFIER"); + b.addEntry().setResource(rpt).getRequest().setMethod(HTTPVerb.PUT).setUrl("DiagnosticReport?identifier=foo|IDENTIFIER"); + mySystemDao.transaction(mySrd, b); + + myObservationDao.read(obs1id); try { - mySystemDao.transaction(mySrd, bundle); + myObservationDao.read(obs2id); fail(); - } catch (InvalidRequestException e) { - assertEquals("Unable to satisfy placeholder ID: urn:uuid:30ce60cf-f7cb-4196-961f-cadafa8b7ff5", e.getMessage()); + } catch (ResourceGoneException e) { + // good } + + rpt = myDiagnosticReportDao.read(rptId); + assertThat(rpt.getResult(), empty()); } - @Test - public void testTransactionDoesNotLeavePlaceholderIds() throws Exception { - String input = IOUtils.toString(getClass().getResourceAsStream("/cdr-bundle.json"), StandardCharsets.UTF_8); - Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); - mySystemDao.transaction(mySrd, bundle); - - IBundleProvider history = mySystemDao.history(null, null, null); - Bundle list = toBundle(history); - ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(list)); - - assertEquals(6, list.getEntry().size()); - - Patient p = find(list, Patient.class, 0); - assertTrue(p.getIdElement().isIdPartValidLong()); - assertTrue(p.getGeneralPractitionerFirstRep().getReferenceElement().isIdPartValidLong()); - } - - @SuppressWarnings("unchecked") - private T find(Bundle theBundle, Class theType, int theIndex) { - int count = 0; - for (BundleEntryComponent nextEntry : theBundle.getEntry()) { - if (nextEntry.getResource() != null && theType.isAssignableFrom(nextEntry.getResource().getClass())) { - if (count == theIndex) { - T t = (T) nextEntry.getResource(); - return t; - } - count++; - } - } - fail(); - return null; - } - - @Test - public void testTransactionFromBundle2() throws Exception { - String input = IOUtils.toString(getClass().getResourceAsStream("/transaction-bundle.xml"), StandardCharsets.UTF_8); - Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); - Bundle response = mySystemDao.transaction(mySrd, bundle); - - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); - assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); - assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern("Practitioner/[0-9]+/_history/1")); - - /* - * Now a second time - */ - - bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); - response = mySystemDao.transaction(mySrd, bundle); - - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); - assertEquals("200 OK", response.getEntry().get(0).getResponse().getStatus()); - assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern("Practitioner/[0-9]+/_history/1")); - - } - - - @Test - public void testTransactionWithNullReference() { - Patient p = new Patient(); - p.addName().addFamily("family"); - final IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - Bundle inputBundle = new Bundle(); - - //@formatter:off - Patient app0 = new Patient(); - app0.addName().addFamily("NEW PATIENT"); - String placeholderId0 = IdDt.newRandomUuid().getValue(); - inputBundle - .addEntry() - .setResource(app0) - .setFullUrl(placeholderId0) - .getRequest() - .setMethod(HTTPVerb.POST) - .setUrl("Patient"); - //@formatter:on - - //@formatter:off - Appointment app1 = new Appointment(); - app1.addParticipant().getActor().setReference(id.getValue()); - inputBundle - .addEntry() - .setResource(app1) - .getRequest() - .setMethod(HTTPVerb.POST) - .setUrl("Appointment"); - //@formatter:on - - //@formatter:off - Appointment app2 = new Appointment(); - app2.addParticipant().getActor().setDisplay("NO REF"); - app2.addParticipant().getActor().setDisplay("YES REF").setReference(placeholderId0); - inputBundle - .addEntry() - .setResource(app2) - .getRequest() - .setMethod(HTTPVerb.POST) - .setUrl("Appointment"); - //@formatter:on - - Bundle outputBundle = mySystemDao.transaction(mySrd, inputBundle); - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(outputBundle)); - - assertEquals(3, outputBundle.getEntry().size()); - IdDt id0 = new IdDt(outputBundle.getEntry().get(0).getResponse().getLocation()); - IdDt id2 = new IdDt(outputBundle.getEntry().get(2).getResponse().getLocation()); - - app2 = myAppointmentDao.read(id2, mySrd); - assertEquals("NO REF", app2.getParticipant().get(0).getActor().getDisplay()); - assertEquals(null, app2.getParticipant().get(0).getActor().getReference()); - assertEquals("YES REF", app2.getParticipant().get(1).getActor().getDisplay()); - assertEquals(id0.toUnqualifiedVersionless().getValue(), app2.getParticipant().get(1).getActor().getReference()); - } - - @Test public void testReindexing() { Patient p = new Patient(); @@ -430,110 +336,30 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertThat(respEntry.getStatus(), startsWith("404")); } - - @Test - public void testTransactionWithInlineMatchUrl() throws Exception { - myDaoConfig.setAllowInlineMatchUrlReferences(true); - - Patient patient = new Patient(); - patient.addIdentifier().setSystem("http://www.ghh.org/identifiers").setValue("condreftestpatid1"); - myPatientDao.create(patient, mySrd); - - String input = IOUtils.toString(getClass().getResourceAsStream("/simone-conditional-url.xml"), StandardCharsets.UTF_8); - Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); - - Bundle response = mySystemDao.transaction(mySrd, bundle); - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); - - } + @Test - public void testTransactionWithInlineMatchUrlMultipleMatches() throws Exception { - myDaoConfig.setAllowInlineMatchUrlReferences(true); - - Patient patient = new Patient(); - patient.addIdentifier().setSystem("http://www.ghh.org/identifiers").setValue("condreftestpatid1"); - myPatientDao.create(patient, mySrd); - - patient = new Patient(); - patient.addIdentifier().setSystem("http://www.ghh.org/identifiers").setValue("condreftestpatid1"); - myPatientDao.create(patient, mySrd); - - String input = IOUtils.toString(getClass().getResourceAsStream("/simone-conditional-url.xml"), StandardCharsets.UTF_8); - Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); - - try { - mySystemDao.transaction(mySrd, bundle); - fail(); - } catch (PreconditionFailedException e) { - assertEquals("Invalid match URL \"Patient?identifier=http://www.ghh.org/identifiers|condreftestpatid1\" - Multiple resources match this search", e.getMessage()); - } - - } - - @Test - public void testTransactionWithInlineMatchUrlNoMatches() throws Exception { - myDaoConfig.setAllowInlineMatchUrlReferences(true); - - String input = IOUtils.toString(getClass().getResourceAsStream("/simone-conditional-url.xml"), StandardCharsets.UTF_8); - Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); - - try { - mySystemDao.transaction(mySrd, bundle); - fail(); - } catch (ResourceNotFoundException e) { - assertEquals("Invalid match URL \"Patient?identifier=http://www.ghh.org/identifiers|condreftestpatid1\" - No resources match this search", e.getMessage()); - } - - } - - @Test - public void testTransactionCreateMatchUrlWithOneMatch() { - String methodName = "testTransactionCreateMatchUrlWithOneMatch"; + public void testTransactionCreateInlineMatchUrlWithNoMatches() { + String methodName = "testTransactionCreateInlineMatchUrlWithNoMatches"; Bundle request = new Bundle(); - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.setId("Patient/" + methodName); - IIdType id = myPatientDao.update(p, mySrd).getId(); - ourLog.info("Created patient, got it: {}", id); - - p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.addName().addFamily("Hello"); - p.setId("Patient/" + methodName); - request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); - + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Observation o = new Observation(); o.getCode().setText("Some Observation"); - o.getSubject().setReference("Patient/" + methodName); + o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName); request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); - Bundle resp = mySystemDao.transaction(mySrd, request); - assertEquals(2, resp.getEntry().size()); - - BundleEntryComponent respEntry = resp.getEntry().get(0); - assertEquals(Constants.STATUS_HTTP_200_OK + " OK", respEntry.getResponse().getStatus()); - assertThat(respEntry.getResponse().getLocation(), endsWith("Patient/" + id.getIdPart() + "/_history/1")); - assertEquals("1", respEntry.getResponse().getEtag()); - - respEntry = resp.getEntry().get(1); - assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); - assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); - assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); - assertEquals("1", respEntry.getResponse().getEtag()); - - o = (Observation) myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); - assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); - assertEquals("1", o.getIdElement().getVersionIdPart()); - + try { + mySystemDao.transaction(mySrd, request); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Invalid match URL \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateInlineMatchUrlWithNoMatches\" - No resources match this search", e.getMessage()); + } } - @After - public void after() { - myDaoConfig.setAllowInlineMatchUrlReferences(false); - } - + @Test public void testTransactionCreateInlineMatchUrlWithOneMatch() { String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch"; @@ -566,7 +392,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertEquals("1", o.getIdElement().getVersionIdPart()); } - + @Test public void testTransactionCreateInlineMatchUrlWithOneMatch2() { String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch2"; @@ -635,27 +461,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } - @Test - public void testTransactionCreateInlineMatchUrlWithNoMatches() { - String methodName = "testTransactionCreateInlineMatchUrlWithNoMatches"; - Bundle request = new Bundle(); - - myDaoConfig.setAllowInlineMatchUrlReferences(true); - - - Observation o = new Observation(); - o.getCode().setText("Some Observation"); - o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName); - request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); - - try { - mySystemDao.transaction(mySrd, request); - fail(); - } catch (ResourceNotFoundException e) { - assertEquals("Invalid match URL \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateInlineMatchUrlWithNoMatches\" - No resources match this search", e.getMessage()); - } - } - @Test public void testTransactionCreateInlineMatchUrlWithTwoMatches() { String methodName = "testTransactionCreateInlineMatchUrlWithTwoMatches"; @@ -683,7 +488,49 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertEquals("Invalid match URL \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateInlineMatchUrlWithTwoMatches\" - Multiple resources match this search", e.getMessage()); } } + + @Test + public void testTransactionCreateMatchUrlWithOneMatch() { + String methodName = "testTransactionCreateMatchUrlWithOneMatch"; + Bundle request = new Bundle(); + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IIdType id = myPatientDao.update(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addFamily("Hello"); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(2, resp.getEntry().size()); + + BundleEntryComponent respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_200_OK + " OK", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), endsWith("Patient/" + id.getIdPart() + "/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + respEntry = resp.getEntry().get(1); + assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + + o = (Observation) myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); + assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); + assertEquals("1", o.getIdElement().getVersionIdPart()); + + } + @Test public void testTransactionCreateMatchUrlWithTwoMatch() { String methodName = "testTransactionCreateMatchUrlWithTwoMatch"; @@ -791,7 +638,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertEquals(Patient.class, details.getResource().getClass()); } - + @Test public void testTransactionCreateWithDuplicateMatchUrl01() { String methodName = "testTransactionCreateWithDuplicateMatchUrl01"; @@ -836,49 +683,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } } - @Test - public void testTransactionCreateWithPutUsingUrl() { - String methodName = "testTransactionCreateWithPutUsingUrl"; - Bundle request = new Bundle(); - request.setType(BundleType.TRANSACTION); - - Observation o = new Observation(); - o.getSubject().setReference("Patient/" + methodName); - request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.PUT).setUrl("Observation/a" + methodName); - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("Patient/" + methodName); - - mySystemDao.transaction(mySrd, request); - - myObservationDao.read(new IdType("Observation/a" + methodName), mySrd); - myPatientDao.read(new IdType("Patient/" + methodName), mySrd); - } - - @Test - public void testTransactionCreateWithPutUsingAbsoluteUrl() { - String methodName = "testTransactionCreateWithPutUsingAbsoluteUrl"; - Bundle request = new Bundle(); - request.setType(BundleType.TRANSACTION); - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("http://localhost/server/base/Patient/" + methodName); - - mySystemDao.transaction(mySrd, request); - - myPatientDao.read(new IdType("Patient/" + methodName), mySrd); - } - - @Test - public void testTransactionCreateWithPutUsingUrl2() throws Exception { - String req = IOUtils.toString(FhirSystemDaoDstu3Test.class.getResourceAsStream("/bundle-dstu3.xml"), StandardCharsets.UTF_8); - Bundle request = myFhirCtx.newXmlParser().parseResource(Bundle.class, req); - mySystemDao.transaction(mySrd, request); - } - - @Test public void testTransactionCreateWithInvalidMatchUrl() { String methodName = "testTransactionCreateWithInvalidMatchUrl"; @@ -952,6 +756,48 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } } + @Test + public void testTransactionCreateWithPutUsingAbsoluteUrl() { + String methodName = "testTransactionCreateWithPutUsingAbsoluteUrl"; + Bundle request = new Bundle(); + request.setType(BundleType.TRANSACTION); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("http://localhost/server/base/Patient/" + methodName); + + mySystemDao.transaction(mySrd, request); + + myPatientDao.read(new IdType("Patient/" + methodName), mySrd); + } + + @Test + public void testTransactionCreateWithPutUsingUrl() { + String methodName = "testTransactionCreateWithPutUsingUrl"; + Bundle request = new Bundle(); + request.setType(BundleType.TRANSACTION); + + Observation o = new Observation(); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.PUT).setUrl("Observation/a" + methodName); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.PUT).setUrl("Patient/" + methodName); + + mySystemDao.transaction(mySrd, request); + + myObservationDao.read(new IdType("Observation/a" + methodName), mySrd); + myPatientDao.read(new IdType("Patient/" + methodName), mySrd); + } + + @Test + public void testTransactionCreateWithPutUsingUrl2() throws Exception { + String req = IOUtils.toString(FhirSystemDaoDstu3Test.class.getResourceAsStream("/bundle-dstu3.xml"), StandardCharsets.UTF_8); + Bundle request = myFhirCtx.newXmlParser().parseResource(Bundle.class, req); + mySystemDao.transaction(mySrd, request); + } + @Test public void testTransactionDeleteByResourceId() { String methodName = "testTransactionDeleteByResourceId"; @@ -1188,6 +1034,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } } + @Test public void testTransactionDeleteMatchUrlWithZeroMatch() { String methodName = "testTransactionDeleteMatchUrlWithZeroMatch"; @@ -1232,6 +1079,43 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } } + @Test + public void testTransactionDoesNotAllowDanglingTemporaryIds() throws Exception { + String input = IOUtils.toString(getClass().getResourceAsStream("/cdr-bundle.json"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + + BundleEntryComponent entry = bundle.addEntry(); + Patient p = new Patient(); + p.getManagingOrganization().setReference("urn:uuid:30ce60cf-f7cb-4196-961f-cadafa8b7ff5"); + entry.setResource(p); + entry.getRequest().setMethod(HTTPVerb.POST); + entry.getRequest().setUrl("Patient"); + + try { + mySystemDao.transaction(mySrd, bundle); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unable to satisfy placeholder ID: urn:uuid:30ce60cf-f7cb-4196-961f-cadafa8b7ff5", e.getMessage()); + } + } + + @Test + public void testTransactionDoesNotLeavePlaceholderIds() throws Exception { + String input = IOUtils.toString(getClass().getResourceAsStream("/cdr-bundle.json"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + mySystemDao.transaction(mySrd, bundle); + + IBundleProvider history = mySystemDao.history(null, null, null); + Bundle list = toBundle(history); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(list)); + + assertEquals(6, list.getEntry().size()); + + Patient p = find(list, Patient.class, 0); + assertTrue(p.getIdElement().isIdPartValidLong()); + assertTrue(p.getGeneralPractitionerFirstRep().getReferenceElement().isIdPartValidLong()); + } + @Test(expected = InvalidRequestException.class) public void testTransactionFailsWithDuplicateIds() { Bundle request = new Bundle(); @@ -1268,44 +1152,26 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertEquals("Patient/temp6789", p.getLink().get(0).getOther().getReference()); } - /** - * Format changed, source isn't valid - */ @Test - @Ignore - public void testTransactionWithBundledValidationSourceAndTarget() throws Exception { - - InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/questionnaire-sdc-profile-example-ussg-fht.xml"); - String bundleStr = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); - Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, bundleStr); - - Bundle resp = mySystemDao.transaction(mySrd, bundle); - - String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp); - ourLog.info(encoded); - - encoded = myFhirCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(resp); - //@formatter:off - assertThat(encoded, containsString("\"response\":{" + - "\"status\":\"201 Created\"," + - "\"location\":\"Questionnaire/54127-6/_history/1\",")); - //@formatter:on + public void testTransactionFromBundle2() throws Exception { + String input = IOUtils.toString(getClass().getResourceAsStream("/transaction-bundle.xml"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + Bundle response = mySystemDao.transaction(mySrd, bundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); + assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); + assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern("Practitioner/[0-9]+/_history/1")); + /* - * Upload again to update + * Now a second time */ - resp = mySystemDao.transaction(mySrd, bundle); - - encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp); - ourLog.info(encoded); - - encoded = myFhirCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(resp); - //@formatter:off - assertThat(encoded, containsString("\"response\":{" + - "\"status\":\"200 OK\"," + - "\"location\":\"Questionnaire/54127-6/_history/2\",")); - //@formatter:on + bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + response = mySystemDao.transaction(mySrd, bundle); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); + assertEquals("200 OK", response.getEntry().get(0).getResponse().getStatus()); + assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern("Practitioner/[0-9]+/_history/1")); } @@ -1367,6 +1233,25 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } + private Bundle testTransactionOrderingCreateBundle(String methodName, int pass, IdType patientPlaceholderId) { + Bundle req = new Bundle(); + req.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?identifier=" + methodName); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(patientPlaceholderId); + obs.addIdentifier().setValue(methodName); + obs.getCode().setText(methodName + pass); + req.addEntry().setResource(obs).getRequest().setMethod(HTTPVerb.PUT).setUrl("Observation?identifier=" + methodName); + + Patient pat = new Patient(); + pat.addIdentifier().setValue(methodName); + pat.addName().addFamily(methodName + pass); + req.addEntry().setResource(pat).setFullUrl(patientPlaceholderId.getValue()).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); + + req.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?identifier=" + methodName); + return req; + } + private void testTransactionOrderingValidateResponse(int pass, Bundle resp) { ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); assertEquals(4, resp.getEntry().size()); @@ -1395,25 +1280,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertThat(respGetBundle.getLink("self").getUrl(), endsWith("/Patient?identifier=testTransactionOrdering")); } - private Bundle testTransactionOrderingCreateBundle(String methodName, int pass, IdType patientPlaceholderId) { - Bundle req = new Bundle(); - req.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?identifier=" + methodName); - - Observation obs = new Observation(); - obs.getSubject().setReferenceElement(patientPlaceholderId); - obs.addIdentifier().setValue(methodName); - obs.getCode().setText(methodName + pass); - req.addEntry().setResource(obs).getRequest().setMethod(HTTPVerb.PUT).setUrl("Observation?identifier=" + methodName); - - Patient pat = new Patient(); - pat.addIdentifier().setValue(methodName); - pat.addName().addFamily(methodName + pass); - req.addEntry().setResource(pat).setFullUrl(patientPlaceholderId.getValue()).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); - - req.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?identifier=" + methodName); - return req; - } - @Test public void testTransactionReadAndSearch() { String methodName = "testTransactionReadAndSearch"; @@ -1746,6 +1612,103 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } + /** + * Format changed, source isn't valid + */ + @Test + @Ignore + public void testTransactionWithBundledValidationSourceAndTarget() throws Exception { + + InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/questionnaire-sdc-profile-example-ussg-fht.xml"); + String bundleStr = IOUtils.toString(bundleRes, StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, bundleStr); + + Bundle resp = mySystemDao.transaction(mySrd, bundle); + + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp); + ourLog.info(encoded); + + encoded = myFhirCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(resp); + //@formatter:off + assertThat(encoded, containsString("\"response\":{" + + "\"status\":\"201 Created\"," + + "\"location\":\"Questionnaire/54127-6/_history/1\",")); + //@formatter:on + + /* + * Upload again to update + */ + + resp = mySystemDao.transaction(mySrd, bundle); + + encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp); + ourLog.info(encoded); + + encoded = myFhirCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(resp); + //@formatter:off + assertThat(encoded, containsString("\"response\":{" + + "\"status\":\"200 OK\"," + + "\"location\":\"Questionnaire/54127-6/_history/2\",")); + //@formatter:on + + } + + @Test + public void testTransactionWithInlineMatchUrl() throws Exception { + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://www.ghh.org/identifiers").setValue("condreftestpatid1"); + myPatientDao.create(patient, mySrd); + + String input = IOUtils.toString(getClass().getResourceAsStream("/simone-conditional-url.xml"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + + Bundle response = mySystemDao.transaction(mySrd, bundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); + + } + + @Test + public void testTransactionWithInlineMatchUrlMultipleMatches() throws Exception { + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://www.ghh.org/identifiers").setValue("condreftestpatid1"); + myPatientDao.create(patient, mySrd); + + patient = new Patient(); + patient.addIdentifier().setSystem("http://www.ghh.org/identifiers").setValue("condreftestpatid1"); + myPatientDao.create(patient, mySrd); + + String input = IOUtils.toString(getClass().getResourceAsStream("/simone-conditional-url.xml"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + + try { + mySystemDao.transaction(mySrd, bundle); + fail(); + } catch (PreconditionFailedException e) { + assertEquals("Invalid match URL \"Patient?identifier=http://www.ghh.org/identifiers|condreftestpatid1\" - Multiple resources match this search", e.getMessage()); + } + + } + + @Test + public void testTransactionWithInlineMatchUrlNoMatches() throws Exception { + myDaoConfig.setAllowInlineMatchUrlReferences(true); + + String input = IOUtils.toString(getClass().getResourceAsStream("/simone-conditional-url.xml"), StandardCharsets.UTF_8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + + try { + mySystemDao.transaction(mySrd, bundle); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Invalid match URL \"Patient?identifier=http://www.ghh.org/identifiers|condreftestpatid1\" - No resources match this search", e.getMessage()); + } + + } + @Test public void testTransactionWithInvalidType() { Bundle request = new Bundle(); @@ -1762,6 +1725,64 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } + @Test + public void testTransactionWithNullReference() { + Patient p = new Patient(); + p.addName().addFamily("family"); + final IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + Bundle inputBundle = new Bundle(); + + //@formatter:off + Patient app0 = new Patient(); + app0.addName().addFamily("NEW PATIENT"); + String placeholderId0 = IdDt.newRandomUuid().getValue(); + inputBundle + .addEntry() + .setResource(app0) + .setFullUrl(placeholderId0) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Patient"); + //@formatter:on + + //@formatter:off + Appointment app1 = new Appointment(); + app1.addParticipant().getActor().setReference(id.getValue()); + inputBundle + .addEntry() + .setResource(app1) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Appointment"); + //@formatter:on + + //@formatter:off + Appointment app2 = new Appointment(); + app2.addParticipant().getActor().setDisplay("NO REF"); + app2.addParticipant().getActor().setDisplay("YES REF").setReference(placeholderId0); + inputBundle + .addEntry() + .setResource(app2) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Appointment"); + //@formatter:on + + Bundle outputBundle = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(outputBundle)); + + assertEquals(3, outputBundle.getEntry().size()); + IdDt id0 = new IdDt(outputBundle.getEntry().get(0).getResponse().getLocation()); + IdDt id2 = new IdDt(outputBundle.getEntry().get(2).getResponse().getLocation()); + + app2 = myAppointmentDao.read(id2, mySrd); + assertEquals("NO REF", app2.getParticipant().get(0).getActor().getDisplay()); + assertEquals(null, app2.getParticipant().get(0).getActor().getReference()); + assertEquals("YES REF", app2.getParticipant().get(1).getActor().getDisplay()); + assertEquals(id0.toUnqualifiedVersionless().getValue(), app2.getParticipant().get(1).getActor().getReference()); + } + @Test public void testTransactionWithReferenceToCreateIfNoneExist() { Bundle bundle = new Bundle(); @@ -1815,6 +1836,46 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertNotEquals(medOrderId1, medOrderId2); } + @Test + public void testTransactionWithRelativeOidIds() throws Exception { + Bundle res = new Bundle(); + res.setType(BundleType.TRANSACTION); + + Patient p1 = new Patient(); + p1.setId("urn:oid:0.1.2.3"); + p1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds01"); + res.addEntry().setResource(p1).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); + + Observation o1 = new Observation(); + o1.setId("cid:observation1"); + o1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds02"); + o1.setSubject(new Reference("urn:oid:0.1.2.3")); + res.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + Observation o2 = new Observation(); + o2.setId("cid:observation2"); + o2.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds03"); + o2.setSubject(new Reference("urn:oid:0.1.2.3")); + res.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + Bundle resp = mySystemDao.transaction(mySrd, res); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); + assertEquals(3, resp.getEntry().size()); + + assertTrue(resp.getEntry().get(0).getResponse().getLocation(), new IdType(resp.getEntry().get(0).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + assertTrue(resp.getEntry().get(1).getResponse().getLocation(), new IdType(resp.getEntry().get(1).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + assertTrue(resp.getEntry().get(2).getResponse().getLocation(), new IdType(resp.getEntry().get(2).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + + o1 = myObservationDao.read(new IdType(resp.getEntry().get(1).getResponse().getLocation()), mySrd); + o2 = myObservationDao.read(new IdType(resp.getEntry().get(2).getResponse().getLocation()), mySrd); + assertThat(o1.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); + assertThat(o2.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); + + } + // // // /** @@ -1917,46 +1978,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { // // } - @Test - public void testTransactionWithRelativeOidIds() throws Exception { - Bundle res = new Bundle(); - res.setType(BundleType.TRANSACTION); - - Patient p1 = new Patient(); - p1.setId("urn:oid:0.1.2.3"); - p1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds01"); - res.addEntry().setResource(p1).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); - - Observation o1 = new Observation(); - o1.setId("cid:observation1"); - o1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds02"); - o1.setSubject(new Reference("urn:oid:0.1.2.3")); - res.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); - - Observation o2 = new Observation(); - o2.setId("cid:observation2"); - o2.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds03"); - o2.setSubject(new Reference("urn:oid:0.1.2.3")); - res.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); - - Bundle resp = mySystemDao.transaction(mySrd, res); - - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); - - assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); - assertEquals(3, resp.getEntry().size()); - - assertTrue(resp.getEntry().get(0).getResponse().getLocation(), new IdType(resp.getEntry().get(0).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); - assertTrue(resp.getEntry().get(1).getResponse().getLocation(), new IdType(resp.getEntry().get(1).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); - assertTrue(resp.getEntry().get(2).getResponse().getLocation(), new IdType(resp.getEntry().get(2).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); - - o1 = myObservationDao.read(new IdType(resp.getEntry().get(1).getResponse().getLocation()), mySrd); - o2 = myObservationDao.read(new IdType(resp.getEntry().get(2).getResponse().getLocation()), mySrd); - assertThat(o1.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); - assertThat(o2.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); - - } - /** * This is not the correct way to do it, but we'll allow it to be lenient */ @@ -2000,4 +2021,9 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 5e635a4e969..5eb2c15ffbe 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -24,6 +24,10 @@ BanUnsupportedHttpMethodsInterceptor was erroring out when a client attempts HTTP HEAD requests + + Conditional URLs in JPA server (e.g. for delete or update) did not support the + _has]]> parameter +