FIx deadlock in transaction processing, and put transaction outcome
in the right spot
This commit is contained in:
parent
6a178b08bd
commit
a13c78d6cc
|
@ -19,48 +19,21 @@ package ca.uhn.fhir.jpa.dao.dstu3;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
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.util.ArrayList;
|
import java.util.*;
|
||||||
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.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.http.NameValuePair;
|
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.*;
|
||||||
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.OperationOutcome.IssueSeverity;
|
||||||
import org.hl7.fhir.dstu3.model.Resource;
|
import org.hl7.fhir.instance.model.api.*;
|
||||||
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.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.*;
|
||||||
import org.springframework.transaction.TransactionDefinition;
|
|
||||||
import org.springframework.transaction.TransactionStatus;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.support.TransactionCallback;
|
import org.springframework.transaction.support.TransactionCallback;
|
||||||
|
@ -69,10 +42,7 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao;
|
import ca.uhn.fhir.jpa.dao.*;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoMethodOutcome;
|
|
||||||
import ca.uhn.fhir.jpa.dao.DeleteMethodOutcome;
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
||||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||||
|
@ -80,19 +50,12 @@ import ca.uhn.fhir.jpa.util.DeleteConflict;
|
||||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
import ca.uhn.fhir.rest.api.*;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.method.*;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.method.BaseMethodBinding;
|
|
||||||
import ca.uhn.fhir.rest.method.BaseResourceReturningMethodBinding;
|
|
||||||
import ca.uhn.fhir.rest.method.BaseResourceReturningMethodBinding.ResourceOrDstu1Bundle;
|
import ca.uhn.fhir.rest.method.BaseResourceReturningMethodBinding.ResourceOrDstu1Bundle;
|
||||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
|
||||||
import ca.uhn.fhir.rest.server.Constants;
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import ca.uhn.fhir.rest.server.exceptions.*;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
|
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.util.FhirTerser;
|
import ca.uhn.fhir.util.FhirTerser;
|
||||||
|
@ -121,6 +84,8 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
|
|
||||||
for (final BundleEntryComponent nextRequestEntry : theRequest.getEntry()) {
|
for (final BundleEntryComponent nextRequestEntry : theRequest.getEntry()) {
|
||||||
|
|
||||||
|
BaseServerResponseExceptionHolder caughtEx = new BaseServerResponseExceptionHolder();
|
||||||
|
|
||||||
TransactionCallback<Bundle> callback = new TransactionCallback<Bundle>() {
|
TransactionCallback<Bundle> callback = new TransactionCallback<Bundle>() {
|
||||||
@Override
|
@Override
|
||||||
public Bundle doInTransaction(TransactionStatus theStatus) {
|
public Bundle doInTransaction(TransactionStatus theStatus) {
|
||||||
|
@ -133,13 +98,12 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BaseServerResponseException caughtEx;
|
|
||||||
try {
|
try {
|
||||||
Bundle nextResponseBundle = txTemplate.execute(callback);
|
Bundle nextResponseBundle = callback.doInTransaction(null);
|
||||||
caughtEx = null;
|
|
||||||
|
|
||||||
BundleEntryComponent subResponseEntry = nextResponseBundle.getEntry().get(0);
|
BundleEntryComponent subResponseEntry = nextResponseBundle.getEntry().get(0);
|
||||||
resp.addEntry(subResponseEntry);
|
resp.addEntry(subResponseEntry);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the individual entry didn't have a resource in its response, bring the sub-transaction's OperationOutcome across so the client can see it
|
* If the individual entry didn't have a resource in its response, bring the sub-transaction's OperationOutcome across so the client can see it
|
||||||
*/
|
*/
|
||||||
|
@ -148,21 +112,19 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (BaseServerResponseException e) {
|
} catch (BaseServerResponseException e) {
|
||||||
caughtEx = e;
|
caughtEx.setException(e);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
ourLog.error("Failure during BATCH sub transaction processing", t);
|
ourLog.error("Failure during BATCH sub transaction processing", t);
|
||||||
caughtEx = new InternalErrorException(t);
|
caughtEx.setException(new InternalErrorException(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (caughtEx != null) {
|
if (caughtEx.getException() != null) {
|
||||||
BundleEntryComponent nextEntry = resp.addEntry();
|
BundleEntryComponent nextEntry = resp.addEntry();
|
||||||
|
|
||||||
OperationOutcome oo = new OperationOutcome();
|
populateEntryWithOperationOutcome(caughtEx.getException(), nextEntry);
|
||||||
oo.addIssue().setSeverity(IssueSeverity.ERROR).setDiagnostics(caughtEx.getMessage());
|
|
||||||
nextEntry.setResource(oo);
|
|
||||||
|
|
||||||
BundleEntryResponseComponent nextEntryResp = nextEntry.getResponse();
|
BundleEntryResponseComponent nextEntryResp = nextEntry.getResponse();
|
||||||
nextEntryResp.setStatus(toStatusString(caughtEx.getStatusCode()));
|
nextEntryResp.setStatus(toStatusString(caughtEx.getException().getStatusCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -173,117 +135,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String extractTransactionUrlOrThrowException(BundleEntryComponent nextEntry, HTTPVerb verb) {
|
private Bundle doTransaction(final ServletRequestDetails theRequestDetails, final Bundle theRequest, final String theActionName) {
|
||||||
String url = nextEntry.getRequest().getUrl();
|
|
||||||
if (isBlank(url)) {
|
|
||||||
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionMissingUrl", verb.name()));
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* TODO: This isn't the most efficient way of doing this.. hopefully we can come up with something better in the future.
|
|
||||||
*/
|
|
||||||
private IBaseResource filterNestedBundle(RequestDetails theRequestDetails, IBaseResource theResource) {
|
|
||||||
IParser p = getContext().newJsonParser();
|
|
||||||
RestfulServerUtils.configureResponseParser(theRequestDetails, p);
|
|
||||||
return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource));
|
|
||||||
}
|
|
||||||
|
|
||||||
private IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
|
|
||||||
IFhirResourceDao<? extends IBaseResource> retVal = getDao(theClass);
|
|
||||||
if (retVal == null) {
|
|
||||||
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + getContext().getResourceDefinition(theClass).getName());
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Meta metaGetOperation(RequestDetails theRequestDetails) {
|
|
||||||
// Notify interceptors
|
|
||||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
|
|
||||||
notifyInterceptors(RestOperationTypeEnum.META, requestDetails);
|
|
||||||
|
|
||||||
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t)";
|
|
||||||
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
|
|
||||||
List<TagDefinition> tagDefinitions = q.getResultList();
|
|
||||||
|
|
||||||
Meta retVal = toMeta(tagDefinitions);
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Meta toMeta(Collection<TagDefinition> tagDefinitions) {
|
|
||||||
Meta retVal = new Meta();
|
|
||||||
for (TagDefinition next : tagDefinitions) {
|
|
||||||
switch (next.getTagType()) {
|
|
||||||
case PROFILE:
|
|
||||||
retVal.addProfile(next.getCode());
|
|
||||||
break;
|
|
||||||
case SECURITY_LABEL:
|
|
||||||
retVal.addSecurity().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
|
|
||||||
break;
|
|
||||||
case TAG:
|
|
||||||
retVal.addTag().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) {
|
|
||||||
RuntimeResourceDefinition resType;
|
|
||||||
try {
|
|
||||||
resType = getContext().getResourceDefinition(theParts.getResourceType());
|
|
||||||
} catch (DataFormatException e) {
|
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
|
|
||||||
throw new InvalidRequestException(msg);
|
|
||||||
}
|
|
||||||
IFhirResourceDao<? extends IBaseResource> dao = null;
|
|
||||||
if (resType != null) {
|
|
||||||
dao = getDao(resType.getImplementingClass());
|
|
||||||
}
|
|
||||||
if (dao == null) {
|
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
|
|
||||||
throw new InvalidRequestException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (theParts.getResourceId() == null && theParts.getParams() == null) {
|
|
||||||
// String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
|
|
||||||
// throw new InvalidRequestException(msg);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return dao;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
|
||||||
@Override
|
|
||||||
public Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest) {
|
|
||||||
if (theRequestDetails != null) {
|
|
||||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null);
|
|
||||||
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
String actionName = "Transaction";
|
|
||||||
return transaction((ServletRequestDetails) theRequestDetails, theRequest, actionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bundle transaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
|
|
||||||
super.markRequestAsProcessingSubRequest(theRequestDetails);
|
|
||||||
try {
|
|
||||||
return doTransaction(theRequestDetails, theRequest, theActionName);
|
|
||||||
} finally {
|
|
||||||
super.clearRequestAsProcessingSubRequest(theRequestDetails);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Bundle doTransaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
|
|
||||||
BundleType transactionType = theRequest.getTypeElement().getValue();
|
BundleType transactionType = theRequest.getTypeElement().getValue();
|
||||||
if (transactionType == BundleType.BATCH) {
|
if (transactionType == BundleType.BATCH) {
|
||||||
return batch(theRequestDetails, theRequest);
|
return batch(theRequestDetails, theRequest);
|
||||||
|
@ -301,11 +153,11 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
ourLog.info("Beginning {} with {} resources", theActionName, theRequest.getEntry().size());
|
ourLog.info("Beginning {} with {} resources", theActionName, theRequest.getEntry().size());
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
Date updateTime = new Date();
|
final Date updateTime = new Date();
|
||||||
|
|
||||||
Set<IdType> allIds = new LinkedHashSet<IdType>();
|
final Set<IdType> allIds = new LinkedHashSet<IdType>();
|
||||||
Map<IdType, IdType> idSubstitutions = new HashMap<IdType, IdType>();
|
final Map<IdType, IdType> idSubstitutions = new HashMap<IdType, IdType>();
|
||||||
Map<IdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<IdType, DaoMethodOutcome>();
|
final Map<IdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<IdType, DaoMethodOutcome>();
|
||||||
|
|
||||||
// Do all entries have a verb?
|
// Do all entries have a verb?
|
||||||
for (int i = 0; i < theRequest.getEntry().size(); i++) {
|
for (int i = 0; i < theRequest.getEntry().size(); i++) {
|
||||||
|
@ -326,9 +178,9 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
* are saved in a two-phase way in order to deal with interdependencies, and
|
* 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
|
* we want the GET processing to use the final indexing state
|
||||||
*/
|
*/
|
||||||
Bundle response = new Bundle();
|
final Bundle response = new Bundle();
|
||||||
List<BundleEntryComponent> getEntries = new ArrayList<BundleEntryComponent>();
|
List<BundleEntryComponent> getEntries = new ArrayList<BundleEntryComponent>();
|
||||||
IdentityHashMap<BundleEntryComponent, Integer> originalRequestOrder = new IdentityHashMap<Bundle.BundleEntryComponent, Integer>();
|
final IdentityHashMap<BundleEntryComponent, Integer> originalRequestOrder = new IdentityHashMap<Bundle.BundleEntryComponent, Integer>();
|
||||||
for (int i = 0; i < theRequest.getEntry().size(); i++) {
|
for (int i = 0; i < theRequest.getEntry().size(); i++) {
|
||||||
originalRequestOrder.put(theRequest.getEntry().get(i), i);
|
originalRequestOrder.put(theRequest.getEntry().get(i), i);
|
||||||
response.addEntry();
|
response.addEntry();
|
||||||
|
@ -338,6 +190,108 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
}
|
}
|
||||||
Collections.sort(theRequest.getEntry(), new TransactionSorter());
|
Collections.sort(theRequest.getEntry(), new TransactionSorter());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All of the write operations in the transaction (PUT, POST, etc.. basically anything
|
||||||
|
* except GET) are performed in their own database transaction before we do the reads.
|
||||||
|
* We do this because the reads (specifically the searches) often spawn their own
|
||||||
|
* secondary database transaction and if we allow that within the primary
|
||||||
|
* database transaction we can end up with deadlocks if the server is under
|
||||||
|
* heavy load with lots of concurrent transactions using all available
|
||||||
|
* database connections.
|
||||||
|
*/
|
||||||
|
TransactionTemplate txManager = new TransactionTemplate(myTxManager);
|
||||||
|
Map<BundleEntryComponent, ResourceTable> entriesToProcess = txManager.execute(new TransactionCallback<Map<BundleEntryComponent, ResourceTable>>() {
|
||||||
|
@Override
|
||||||
|
public Map<BundleEntryComponent, ResourceTable> doInTransaction(TransactionStatus status) {
|
||||||
|
return doTransactionWriteOperations(theRequestDetails, theRequest, theActionName, updateTime, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (Entry<BundleEntryComponent, ResourceTable> nextEntry : entriesToProcess.entrySet()) {
|
||||||
|
String responseLocation = nextEntry.getValue().getIdDt().toUnqualified().getValue();
|
||||||
|
String responseEtag = nextEntry.getValue().getIdDt().getVersionIdPart();
|
||||||
|
nextEntry.getKey().getResponse().setLocation(responseLocation);
|
||||||
|
nextEntry.getKey().getResponse().setEtag(responseEtag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Loop through the request and process any entries of type GET
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < getEntries.size(); i++) {
|
||||||
|
BundleEntryComponent nextReqEntry = getEntries.get(i);
|
||||||
|
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
|
||||||
|
BundleEntryComponent nextRespEntry = response.getEntry().get(originalOrder);
|
||||||
|
|
||||||
|
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
|
||||||
|
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
|
||||||
|
requestDetails.setRequestType(RequestTypeEnum.GET);
|
||||||
|
requestDetails.setServer(theRequestDetails.getServer());
|
||||||
|
|
||||||
|
String url = extractTransactionUrlOrThrowException(nextReqEntry, HTTPVerb.GET);
|
||||||
|
|
||||||
|
int qIndex = url.indexOf('?');
|
||||||
|
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
|
||||||
|
requestDetails.setParameters(new HashMap<String, String[]>());
|
||||||
|
if (qIndex != -1) {
|
||||||
|
String params = url.substring(qIndex);
|
||||||
|
List<NameValuePair> parameters = translateMatchUrl(params);
|
||||||
|
for (NameValuePair next : parameters) {
|
||||||
|
paramValues.put(next.getName(), next.getValue());
|
||||||
|
}
|
||||||
|
for (java.util.Map.Entry<String, Collection<String>> nextParamEntry : paramValues.asMap().entrySet()) {
|
||||||
|
String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
|
||||||
|
requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue);
|
||||||
|
}
|
||||||
|
url = url.substring(0, qIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestDetails.setRequestPath(url);
|
||||||
|
requestDetails.setFhirServerBase(theRequestDetails.getFhirServerBase());
|
||||||
|
|
||||||
|
theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url);
|
||||||
|
BaseMethodBinding<?> method = theRequestDetails.getServer().determineResourceMethod(requestDetails, url);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
if (isNotBlank(nextReqEntry.getRequest().getIfNoneExist())) {
|
||||||
|
requestDetails.addHeader(Constants.HEADER_IF_NONE_EXIST, nextReqEntry.getRequest().getIfNoneExist());
|
||||||
|
}
|
||||||
|
if (isNotBlank(nextReqEntry.getRequest().getIfNoneMatch())) {
|
||||||
|
requestDetails.addHeader(Constants.HEADER_IF_NONE_MATCH, nextReqEntry.getRequest().getIfNoneMatch());
|
||||||
|
}
|
||||||
|
|
||||||
|
Validate.isTrue(method instanceof BaseResourceReturningMethodBinding, "Unable to handle GET {}", url);
|
||||||
|
try {
|
||||||
|
ResourceOrDstu1Bundle responseData = ((BaseResourceReturningMethodBinding) method).doInvokeServer(theRequestDetails.getServer(), requestDetails);
|
||||||
|
IBaseResource resource = responseData.getResource();
|
||||||
|
if (paramValues.containsKey(Constants.PARAM_SUMMARY) || paramValues.containsKey(Constants.PARAM_CONTENT)) {
|
||||||
|
resource = filterNestedBundle(requestDetails, resource);
|
||||||
|
}
|
||||||
|
nextRespEntry.setResource((Resource) resource);
|
||||||
|
nextRespEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
|
||||||
|
} catch (NotModifiedException e) {
|
||||||
|
nextRespEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED));
|
||||||
|
} catch (BaseServerResponseException e) {
|
||||||
|
ourLog.info("Failure processing transaction GET {}: {}", url, e.toString());
|
||||||
|
nextRespEntry.getResponse().setStatus(toStatusString(e.getStatusCode()));
|
||||||
|
populateEntryWithOperationOutcome(e, nextRespEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
long delay = System.currentTimeMillis() - start;
|
||||||
|
ourLog.info(theActionName + " completed in {}ms", new Object[] { delay });
|
||||||
|
|
||||||
|
response.setType(BundleType.TRANSACTIONRESPONSE);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date updateTime, Set<IdType> allIds,
|
||||||
|
Map<IdType, IdType> idSubstitutions, Map<IdType, DaoMethodOutcome> idToPersistedOutcome, Bundle response, IdentityHashMap<BundleEntryComponent, Integer> originalRequestOrder) {
|
||||||
Set<String> deletedResources = new HashSet<String>();
|
Set<String> deletedResources = new HashSet<String>();
|
||||||
List<DeleteConflict> deleteConflicts = new ArrayList<DeleteConflict>();
|
List<DeleteConflict> deleteConflicts = new ArrayList<DeleteConflict>();
|
||||||
Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<BundleEntryComponent, ResourceTable>();
|
Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<BundleEntryComponent, ResourceTable>();
|
||||||
|
@ -562,87 +516,122 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
}
|
}
|
||||||
ourLog.info("Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"", next, replacement);
|
ourLog.info("Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"", next, replacement);
|
||||||
}
|
}
|
||||||
|
return entriesToProcess;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
private String extractTransactionUrlOrThrowException(BundleEntryComponent nextEntry, HTTPVerb verb) {
|
||||||
* Loop through the request and process any entries of type GET
|
String url = nextEntry.getRequest().getUrl();
|
||||||
*/
|
if (isBlank(url)) {
|
||||||
for (int i = 0; i < getEntries.size(); i++) {
|
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionMissingUrl", verb.name()));
|
||||||
BundleEntryComponent nextReqEntry = getEntries.get(i);
|
}
|
||||||
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
|
return url;
|
||||||
BundleEntryComponent nextRespEntry = response.getEntry().get(originalOrder);
|
}
|
||||||
|
|
||||||
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
|
/**
|
||||||
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
|
* This method is called for nested bundles (e.g. if we received a transaction with an entry that
|
||||||
requestDetails.setRequestType(RequestTypeEnum.GET);
|
* was a GET search, this method is called on the bundle for the search result, that will be placed in the
|
||||||
requestDetails.setServer(theRequestDetails.getServer());
|
* outer bundle). This method applies the _summary and _content parameters to the output of
|
||||||
|
* that bundle.
|
||||||
|
*
|
||||||
|
* TODO: This isn't the most efficient way of doing this.. hopefully we can come up with something better in the future.
|
||||||
|
*/
|
||||||
|
private IBaseResource filterNestedBundle(RequestDetails theRequestDetails, IBaseResource theResource) {
|
||||||
|
IParser p = getContext().newJsonParser();
|
||||||
|
RestfulServerUtils.configureResponseParser(theRequestDetails, p);
|
||||||
|
return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource));
|
||||||
|
}
|
||||||
|
|
||||||
String url = extractTransactionUrlOrThrowException(nextReqEntry, HTTPVerb.GET);
|
private IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
|
||||||
|
IFhirResourceDao<? extends IBaseResource> retVal = getDao(theClass);
|
||||||
|
if (retVal == null) {
|
||||||
|
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + getContext().getResourceDefinition(theClass).getName());
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
int qIndex = url.indexOf('?');
|
@Override
|
||||||
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
|
public Meta metaGetOperation(RequestDetails theRequestDetails) {
|
||||||
requestDetails.setParameters(new HashMap<String, String[]>());
|
// Notify interceptors
|
||||||
if (qIndex != -1) {
|
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
|
||||||
String params = url.substring(qIndex);
|
notifyInterceptors(RestOperationTypeEnum.META, requestDetails);
|
||||||
List<NameValuePair> parameters = translateMatchUrl(params);
|
|
||||||
for (NameValuePair next : parameters) {
|
|
||||||
paramValues.put(next.getName(), next.getValue());
|
|
||||||
}
|
|
||||||
for (java.util.Map.Entry<String, Collection<String>> nextParamEntry : paramValues.asMap().entrySet()) {
|
|
||||||
String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
|
|
||||||
requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue);
|
|
||||||
}
|
|
||||||
url = url.substring(0, qIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestDetails.setRequestPath(url);
|
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t)";
|
||||||
requestDetails.setFhirServerBase(theRequestDetails.getFhirServerBase());
|
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
|
||||||
|
List<TagDefinition> tagDefinitions = q.getResultList();
|
||||||
|
|
||||||
theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url);
|
Meta retVal = toMeta(tagDefinitions);
|
||||||
BaseMethodBinding<?> method = theRequestDetails.getServer().determineResourceMethod(requestDetails, url);
|
|
||||||
if (method == null) {
|
|
||||||
throw new IllegalArgumentException("Unable to handle GET " + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNotBlank(nextReqEntry.getRequest().getIfMatch())) {
|
return retVal;
|
||||||
requestDetails.addHeader(Constants.HEADER_IF_MATCH, nextReqEntry.getRequest().getIfMatch());
|
}
|
||||||
}
|
|
||||||
if (isNotBlank(nextReqEntry.getRequest().getIfNoneExist())) {
|
|
||||||
requestDetails.addHeader(Constants.HEADER_IF_NONE_EXIST, nextReqEntry.getRequest().getIfNoneExist());
|
|
||||||
}
|
|
||||||
if (isNotBlank(nextReqEntry.getRequest().getIfNoneMatch())) {
|
|
||||||
requestDetails.addHeader(Constants.HEADER_IF_NONE_MATCH, nextReqEntry.getRequest().getIfNoneMatch());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method instanceof BaseResourceReturningMethodBinding) {
|
private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, BundleEntryComponent nextEntry) {
|
||||||
try {
|
OperationOutcome oo = new OperationOutcome();
|
||||||
ResourceOrDstu1Bundle responseData = ((BaseResourceReturningMethodBinding) method).doInvokeServer(theRequestDetails.getServer(), requestDetails);
|
oo.addIssue().setSeverity(IssueSeverity.ERROR).setDiagnostics(caughtEx.getMessage());
|
||||||
IBaseResource resource = responseData.getResource();
|
nextEntry.getResponse().setOutcome(oo);
|
||||||
if (paramValues.containsKey(Constants.PARAM_SUMMARY) || paramValues.containsKey(Constants.PARAM_CONTENT)) {
|
}
|
||||||
resource = filterNestedBundle(requestDetails, resource);
|
|
||||||
}
|
|
||||||
nextRespEntry.setResource((Resource) resource);
|
|
||||||
nextRespEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
|
|
||||||
} catch (NotModifiedException e) {
|
|
||||||
nextRespEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Unable to handle GET " + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) {
|
||||||
|
RuntimeResourceDefinition resType;
|
||||||
|
try {
|
||||||
|
resType = getContext().getResourceDefinition(theParts.getResourceType());
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
IFhirResourceDao<? extends IBaseResource> dao = null;
|
||||||
|
if (resType != null) {
|
||||||
|
dao = getDao(resType.getImplementingClass());
|
||||||
|
}
|
||||||
|
if (dao == null) {
|
||||||
|
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Entry<BundleEntryComponent, ResourceTable> nextEntry : entriesToProcess.entrySet()) {
|
// if (theParts.getResourceId() == null && theParts.getParams() == null) {
|
||||||
String responseLocation = nextEntry.getValue().getIdDt().toUnqualified().getValue();
|
// String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
|
||||||
String responseEtag = nextEntry.getValue().getIdDt().getVersionIdPart();
|
// throw new InvalidRequestException(msg);
|
||||||
nextEntry.getKey().getResponse().setLocation(responseLocation);
|
// }
|
||||||
nextEntry.getKey().getResponse().setEtag(responseEtag);
|
|
||||||
|
return dao;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Meta toMeta(Collection<TagDefinition> tagDefinitions) {
|
||||||
|
Meta retVal = new Meta();
|
||||||
|
for (TagDefinition next : tagDefinitions) {
|
||||||
|
switch (next.getTagType()) {
|
||||||
|
case PROFILE:
|
||||||
|
retVal.addProfile(next.getCode());
|
||||||
|
break;
|
||||||
|
case SECURITY_LABEL:
|
||||||
|
retVal.addSecurity().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
|
||||||
|
break;
|
||||||
|
case TAG:
|
||||||
|
retVal.addTag().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(propagation = Propagation.NEVER)
|
||||||
|
@Override
|
||||||
|
public Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest) {
|
||||||
|
if (theRequestDetails != null) {
|
||||||
|
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null);
|
||||||
|
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
long delay = System.currentTimeMillis() - start;
|
String actionName = "Transaction";
|
||||||
ourLog.info(theActionName + " completed in {}ms", new Object[] { delay });
|
return transaction((ServletRequestDetails) theRequestDetails, theRequest, actionName);
|
||||||
|
}
|
||||||
|
|
||||||
response.setType(BundleType.TRANSACTIONRESPONSE);
|
private Bundle transaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
|
||||||
return response;
|
super.markRequestAsProcessingSubRequest(theRequestDetails);
|
||||||
|
try {
|
||||||
|
return doTransaction(theRequestDetails, theRequest, theActionName);
|
||||||
|
} finally {
|
||||||
|
super.clearRequestAsProcessingSubRequest(theRequestDetails);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void handleTransactionCreateOrUpdateOutcome(Map<IdType, IdType> idSubstitutions, Map<IdType, DaoMethodOutcome> idToPersistedOutcome, IdType nextResourceId, DaoMethodOutcome outcome,
|
private static void handleTransactionCreateOrUpdateOutcome(Map<IdType, IdType> idSubstitutions, Map<IdType, DaoMethodOutcome> idToPersistedOutcome, IdType nextResourceId, DaoMethodOutcome outcome,
|
||||||
|
@ -693,6 +682,19 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
||||||
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
|
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class BaseServerResponseExceptionHolder
|
||||||
|
{
|
||||||
|
private BaseServerResponseException myException;
|
||||||
|
|
||||||
|
public BaseServerResponseException getException() {
|
||||||
|
return myException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setException(BaseServerResponseException myException) {
|
||||||
|
this.myException = myException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
/**
|
/**
|
||||||
* Transaction Order, per the spec:
|
* Transaction Order, per the spec:
|
||||||
|
|
|
@ -34,12 +34,10 @@ import javax.persistence.SequenceGenerator;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
import javax.persistence.UniqueConstraint;
|
import javax.persistence.UniqueConstraint;
|
||||||
|
|
||||||
//@formatter:off
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "HFJ_SEARCH_RESULT", uniqueConstraints= {
|
@Table(name = "HFJ_SEARCH_RESULT", uniqueConstraints= {
|
||||||
@UniqueConstraint(name="IDX_SEARCHRES_ORDER", columnNames= {"SEARCH_PID", "SEARCH_ORDER"})
|
@UniqueConstraint(name="IDX_SEARCHRES_ORDER", columnNames= {"SEARCH_PID", "SEARCH_ORDER"})
|
||||||
})
|
})
|
||||||
//@formatter:on
|
|
||||||
public class SearchResult implements Serializable {
|
public class SearchResult implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
package ca.uhn.fhir.jpa.search;
|
package ca.uhn.fhir.jpa.search;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2017 University Health Network
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
|
|
|
@ -44,6 +44,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
||||||
* starvation
|
* starvation
|
||||||
*/
|
*/
|
||||||
int maxThreads = (int)(Math.random() * 6) + 2;
|
int maxThreads = (int)(Math.random() * 6) + 2;
|
||||||
|
maxThreads = 2;
|
||||||
retVal.setMaxTotal(maxThreads);
|
retVal.setMaxTotal(maxThreads);
|
||||||
|
|
||||||
DataSource dataSource = ProxyDataSourceBuilder
|
DataSource dataSource = ProxyDataSourceBuilder
|
||||||
|
|
|
@ -26,31 +26,14 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.hl7.fhir.dstu3.model.Appointment;
|
import org.hl7.fhir.dstu3.model.*;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle;
|
|
||||||
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
|
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent;
|
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryResponseComponent;
|
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryResponseComponent;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
|
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb;
|
import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb;
|
||||||
import org.hl7.fhir.dstu3.model.Coding;
|
|
||||||
import org.hl7.fhir.dstu3.model.Condition;
|
|
||||||
import org.hl7.fhir.dstu3.model.DiagnosticReport;
|
|
||||||
import org.hl7.fhir.dstu3.model.Encounter;
|
|
||||||
import org.hl7.fhir.dstu3.model.EpisodeOfCare;
|
|
||||||
import org.hl7.fhir.dstu3.model.IdType;
|
|
||||||
import org.hl7.fhir.dstu3.model.Medication;
|
|
||||||
import org.hl7.fhir.dstu3.model.MedicationRequest;
|
|
||||||
import org.hl7.fhir.dstu3.model.Meta;
|
|
||||||
import org.hl7.fhir.dstu3.model.Observation;
|
|
||||||
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
|
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
|
||||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
|
||||||
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
|
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
|
||||||
import org.hl7.fhir.dstu3.model.Organization;
|
|
||||||
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.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
@ -497,9 +480,10 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
||||||
assertThat(resp.getEntry().get(0).getResponse().getLocation(), startsWith("Patient/"));
|
assertThat(resp.getEntry().get(0).getResponse().getLocation(), startsWith("Patient/"));
|
||||||
|
|
||||||
// Bundle.entry[1] is failed read response
|
// Bundle.entry[1] is failed read response
|
||||||
assertEquals(OperationOutcome.class, resp.getEntry().get(1).getResource().getClass());
|
Resource oo = resp.getEntry().get(1).getResponse().getOutcome();
|
||||||
assertEquals(IssueSeverity.ERROR, ((OperationOutcome) resp.getEntry().get(1).getResource()).getIssue().get(0).getSeverityElement().getValue());
|
assertEquals(OperationOutcome.class, oo.getClass());
|
||||||
assertEquals("Resource Patient/THIS_ID_DOESNT_EXIST is not known", ((OperationOutcome) resp.getEntry().get(1).getResource()).getIssue().get(0).getDiagnostics());
|
assertEquals(IssueSeverity.ERROR, ((OperationOutcome) oo).getIssue().get(0).getSeverityElement().getValue());
|
||||||
|
assertEquals("Resource Patient/THIS_ID_DOESNT_EXIST is not known", ((OperationOutcome) oo).getIssue().get(0).getDiagnostics());
|
||||||
assertEquals("404 Not Found", resp.getEntry().get(1).getResponse().getStatus());
|
assertEquals("404 Not Found", resp.getEntry().get(1).getResponse().getStatus());
|
||||||
|
|
||||||
// Check POST
|
// Check POST
|
||||||
|
@ -891,6 +875,143 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionCreateWithBadRead() {
|
||||||
|
Bundle request = new Bundle();
|
||||||
|
request.setType(BundleType.TRANSACTION);
|
||||||
|
|
||||||
|
Patient p;
|
||||||
|
p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||||
|
request
|
||||||
|
.addEntry()
|
||||||
|
.setResource(p)
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.POST)
|
||||||
|
.setUrl("Patient");
|
||||||
|
|
||||||
|
request
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient/BABABABA");
|
||||||
|
|
||||||
|
Bundle response = mySystemDao.transaction(mySrd, request);
|
||||||
|
assertEquals(2, response.getEntry().size());
|
||||||
|
|
||||||
|
assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus());
|
||||||
|
assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern(".*Patient/[0-9]+.*"));
|
||||||
|
assertEquals("404 Not Found", response.getEntry().get(1).getResponse().getStatus());
|
||||||
|
|
||||||
|
OperationOutcome oo = (OperationOutcome) response.getEntry().get(1).getResponse().getOutcome();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity());
|
||||||
|
assertEquals("Resource Patient/BABABABA is not known", oo.getIssue().get(0).getDiagnostics());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionCreateWithBadSearch() {
|
||||||
|
Bundle request = new Bundle();
|
||||||
|
request.setType(BundleType.TRANSACTION);
|
||||||
|
|
||||||
|
Patient p;
|
||||||
|
p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||||
|
request
|
||||||
|
.addEntry()
|
||||||
|
.setResource(p)
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.POST)
|
||||||
|
.setUrl("Patient");
|
||||||
|
|
||||||
|
request
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient?foobadparam=1");
|
||||||
|
|
||||||
|
Bundle response = mySystemDao.transaction(mySrd, request);
|
||||||
|
assertEquals(2, response.getEntry().size());
|
||||||
|
|
||||||
|
assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus());
|
||||||
|
assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern(".*Patient/[0-9]+.*"));
|
||||||
|
assertEquals("400 Bad Request", response.getEntry().get(1).getResponse().getStatus());
|
||||||
|
|
||||||
|
OperationOutcome oo = (OperationOutcome) response.getEntry().get(1).getResponse().getOutcome();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity());
|
||||||
|
assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Unknown search parameter"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchCreateWithBadRead() {
|
||||||
|
Bundle request = new Bundle();
|
||||||
|
request.setType(BundleType.BATCH);
|
||||||
|
|
||||||
|
Patient p;
|
||||||
|
p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||||
|
request
|
||||||
|
.addEntry()
|
||||||
|
.setResource(p)
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.POST)
|
||||||
|
.setUrl("Patient");
|
||||||
|
|
||||||
|
request
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient/BABABABA");
|
||||||
|
|
||||||
|
Bundle response = mySystemDao.transaction(mySrd, request);
|
||||||
|
assertEquals(2, response.getEntry().size());
|
||||||
|
|
||||||
|
assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus());
|
||||||
|
assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern(".*Patient/[0-9]+.*"));
|
||||||
|
assertEquals("404 Not Found", response.getEntry().get(1).getResponse().getStatus());
|
||||||
|
|
||||||
|
OperationOutcome oo = (OperationOutcome) response.getEntry().get(1).getResponse().getOutcome();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity());
|
||||||
|
assertEquals("Resource Patient/BABABABA is not known", oo.getIssue().get(0).getDiagnostics());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchCreateWithBadSearch() {
|
||||||
|
Bundle request = new Bundle();
|
||||||
|
request.setType(BundleType.BATCH);
|
||||||
|
|
||||||
|
Patient p;
|
||||||
|
p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||||
|
request
|
||||||
|
.addEntry()
|
||||||
|
.setResource(p)
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.POST)
|
||||||
|
.setUrl("Patient");
|
||||||
|
|
||||||
|
request
|
||||||
|
.addEntry()
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.GET)
|
||||||
|
.setUrl("Patient?foobadparam=1");
|
||||||
|
|
||||||
|
Bundle response = mySystemDao.transaction(mySrd, request);
|
||||||
|
assertEquals(2, response.getEntry().size());
|
||||||
|
|
||||||
|
assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus());
|
||||||
|
assertThat(response.getEntry().get(0).getResponse().getLocation(), matchesPattern(".*Patient/[0-9]+.*"));
|
||||||
|
assertEquals("400 Bad Request", response.getEntry().get(1).getResponse().getStatus());
|
||||||
|
|
||||||
|
OperationOutcome oo = (OperationOutcome) response.getEntry().get(1).getResponse().getOutcome();
|
||||||
|
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity());
|
||||||
|
assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Unknown search parameter"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTransactionCreateWithDuplicateMatchUrl02() {
|
public void testTransactionCreateWithDuplicateMatchUrl02() {
|
||||||
String methodName = "testTransactionCreateWithDuplicateMatchUrl02";
|
String methodName = "testTransactionCreateWithDuplicateMatchUrl02";
|
||||||
|
@ -1331,20 +1452,15 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTransactionDoesNotLeavePlaceholderIds() throws Exception {
|
public void testTransactionDoesNotLeavePlaceholderIds() throws Exception {
|
||||||
newTxTemplate().execute(new TransactionCallbackWithoutResult() {
|
String input;
|
||||||
@Override
|
try {
|
||||||
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
|
input = IOUtils.toString(getClass().getResourceAsStream("/cdr-bundle.json"), StandardCharsets.UTF_8);
|
||||||
String input;
|
} catch (IOException e) {
|
||||||
try {
|
fail(e.toString());
|
||||||
input = IOUtils.toString(getClass().getResourceAsStream("/cdr-bundle.json"), StandardCharsets.UTF_8);
|
return;
|
||||||
} catch (IOException e) {
|
}
|
||||||
fail(e.toString());
|
Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input);
|
||||||
return;
|
mySystemDao.transaction(mySrd, bundle);
|
||||||
}
|
|
||||||
Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input);
|
|
||||||
mySystemDao.transaction(mySrd, bundle);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
IBundleProvider history = mySystemDao.history(null, null, null);
|
IBundleProvider history = mySystemDao.history(null, null, null);
|
||||||
Bundle list = toBundle(history);
|
Bundle list = toBundle(history);
|
||||||
|
|
|
@ -177,8 +177,17 @@
|
||||||
GitHub user @CarthageKing for the pull request!
|
GitHub user @CarthageKing for the pull request!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Fix potential deadlock in stale search deleting task in JPA server
|
Fix potential deadlock in stale search deleting task in JPA server, as well
|
||||||
|
as potential deadlock when executing transactions containing nested
|
||||||
|
searches when operating under extremely heavy load.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
JPA server transaction operations now put OperationOutcome resources resulting
|
||||||
|
from actions in
|
||||||
|
<![CDATA[<code>Bundle.entry.response.outcome</code>]]>
|
||||||
|
instead of the previous
|
||||||
|
<![CDATA[<code>Bundle.entry.resource</code>]]>
|
||||||
|
<action>
|
||||||
</release>
|
</release>
|
||||||
<release version="2.5" date="2017-06-08">
|
<release version="2.5" date="2017-06-08">
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
|
Loading…
Reference in New Issue