From 315ad439e497f35565a263dc20bceeff41c32d67 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Mon, 23 Feb 2015 09:08:59 -0500 Subject: [PATCH 1/2] Work on adding conditional HTTP operations to client and JPA server --- .../java/ca/uhn/fhir/context/FhirContext.java | 28 +- .../ca/uhn/fhir/context/ModelScanner.java | 2 +- .../ca/uhn/fhir/model/primitive/IdDt.java | 19 + .../ca/uhn/fhir/rest/api/MethodOutcome.java | 3 +- .../ca/uhn/fhir/rest/client/BaseClient.java | 15 +- .../rest/client/BaseHttpClientInvocation.java | 3 + .../uhn/fhir/rest/client/GenericClient.java | 191 ++- .../ca/uhn/fhir/rest/gclient/IBaseQuery.java | 9 + .../uhn/fhir/rest/gclient/ICreateTyped.java | 16 + .../fhir/rest/gclient/ICreateWithQuery.java | 6 + .../rest/gclient/ICreateWithQueryTyped.java | 5 + .../ca/uhn/fhir/rest/gclient/IDelete.java | 18 +- .../fhir/rest/gclient/IDeleteWithQuery.java | 5 + .../rest/gclient/IDeleteWithQueryTyped.java | 5 + .../java/ca/uhn/fhir/rest/gclient/IQuery.java | 6 +- .../uhn/fhir/rest/gclient/IUpdateTyped.java | 16 + .../fhir/rest/gclient/IUpdateWithQuery.java | 6 + .../rest/gclient/IUpdateWithQueryTyped.java | 5 + .../BaseHttpClientInvocationWithContents.java | 86 +- .../fhir/rest/method/DeleteMethodBinding.java | 11 + .../method/HttpDeleteClientInvocation.java | 14 +- .../rest/method/HttpPostClientInvocation.java | 14 +- .../rest/method/HttpPutClientInvocation.java | 7 +- .../ca/uhn/fhir/rest/method/MethodUtil.java | 682 ++++---- .../ca/uhn/fhir/rest/server/Constants.java | 2 + .../ca/uhn/fhir/i18n/hapi-messages.properties | 17 +- .../.settings/org.eclipse.jdt.core.prefs | 6 +- .../java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java | 227 ++- .../uhn/fhir/jpa/dao/BaseFhirSystemDao.java | 63 - .../ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java | 36 + .../ca/uhn/fhir/jpa/dao/FhirResourceDao.java | 1413 +++++++++-------- .../uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java | 701 +++++--- .../ca/uhn/fhir/jpa/dao/IFhirResourceDao.java | 46 +- .../java/ca/uhn/fhir/jpa/entity/ForcedId.java | 10 +- .../provider/JpaConformanceProviderDstu1.java | 5 +- .../provider/JpaConformanceProviderDstu2.java | 5 +- .../jpa/provider/JpaResourceProvider.java | 7 +- .../uhn/fhir/jpa/dao/FhirResourceDaoTest.java | 277 +++- .../fhir/jpa/dao/FhirSystemDaoDstu1Test.java | 414 +++++ .../fhir/jpa/dao/FhirSystemDaoDstu2Test.java | 681 ++++++++ .../uhn/fhir/jpa/dao/FhirSystemDaoTest.java | 899 ----------- .../provider/ResourceProviderDstu2Test.java | 1 - .../src/test/resources/bundle-dstu1.xml | 158 ++ .../rest/client/GenericClientTestDev.java | 74 - .../rest/client/GenericClientTestDstu2.java | 212 +++ .../rest/server/UpdateConditionalTest.java | 133 ++ 46 files changed, 4083 insertions(+), 2476 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseQuery.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQuery.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQueryTyped.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQuery.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQueryTyped.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQuery.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQueryTyped.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java delete mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/bundle-dstu1.xml delete mode 100644 hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDev.java create mode 100644 hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDstu2.java create mode 100644 hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/UpdateConditionalTest.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index e7f909fdaaa..109b3af1044 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -72,6 +72,7 @@ import ca.uhn.fhir.validation.FhirValidator; public class FhirContext { private static final List> EMPTY_LIST = Collections.emptyList(); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class); private volatile Map, BaseRuntimeElementDefinition> myClassToElementDefinition = Collections.emptyMap(); private volatile Map myIdToResourceDefinition = Collections.emptyMap(); private HapiLocalizer myLocalizer = new HapiLocalizer(); @@ -81,6 +82,7 @@ public class FhirContext { private volatile IRestfulClientFactory myRestfulClientFactory; private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; private final IFhirVersion myVersion; + private Map>> myVersionToNameToResourceType = Collections.emptyMap(); /** @@ -123,7 +125,9 @@ public class FhirContext { } else { throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructures")); } - + + ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name()); + scanResourceTypes(toElementList(theResourceTypes)); } @@ -176,7 +180,7 @@ public class FhirContext { if (Modifier.isAbstract(theResourceType.getModifiers())) { throw new IllegalArgumentException("Can not scan abstract or interface class (resource definitions must be concrete classes): " + theResourceType.getName()); } - + RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType); if (retVal == null) { retVal = scanResourceType((Class) theResourceType); @@ -186,7 +190,7 @@ public class FhirContext { public RuntimeResourceDefinition getResourceDefinition(FhirVersionEnum theVersion, String theResourceName) { Validate.notNull(theVersion, "theVersion can not be null"); - + if (theVersion.equals(myVersion.getVersion())) { return getResourceDefinition(theResourceName); } @@ -195,18 +199,18 @@ public class FhirContext { if (nameToType == null) { nameToType = new HashMap>(); ModelScanner.scanVersionPropertyFile(null, nameToType, theVersion); - - Map>> newVersionToNameToResourceType = new HashMap>>(); + + Map>> newVersionToNameToResourceType = new HashMap>>(); newVersionToNameToResourceType.putAll(myVersionToNameToResourceType); newVersionToNameToResourceType.put(theVersion, nameToType); myVersionToNameToResourceType = newVersionToNameToResourceType; } - + Class resourceType = nameToType.get(theResourceName.toLowerCase()); - if (resourceType==null) { + if (resourceType == null) { throw new DataFormatException(createUnknownResourceNameError(theResourceName, theVersion)); } - + return getResourceDefinition(resourceType); } @@ -457,6 +461,10 @@ public class FhirContext { return new FhirContext(FhirVersionEnum.DSTU2); } + public static FhirContext forDstu2Hl7Org() { + return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG); + } + private static Collection> toCollection(Class theResourceType) { ArrayList> retVal = new ArrayList>(1); retVal.add(theResourceType); @@ -475,8 +483,4 @@ public class FhirContext { return retVal; } - public static FhirContext forDstu2Hl7Org() { - return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG); - } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index 4dc9edef6d9..f5b674d3754 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -216,7 +216,7 @@ class ModelScanner { long time = System.currentTimeMillis() - start; int size = myClassToElementDefinitions.size() - startSize; - ourLog.info("Done scanning FHIR library, found {} model entries in {}ms", size, time); + ourLog.debug("Done scanning FHIR library, found {} model entries in {}ms", size, time); } /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java index 706745e3dd0..a1e3aaf5a29 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java @@ -113,6 +113,18 @@ public class IdDt extends UriDt implements IPrimitiveDatatype { this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart)); } + /** + * Constructor + * + * @param theResourceType + * The resource type (e.g. "Patient") + * @param theIdPart + * The ID (e.g. "123") + */ + public IdDt(String theResourceType, Long theIdPart) { + this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart)); + } + /** * Constructor * @@ -515,6 +527,13 @@ public class IdDt extends UriDt implements IPrimitiveDatatype { return theIdPart.toPlainString(); } + private static String toPlainStringWithNpeThrowIfNeeded(Long theIdPart) { + if (theIdPart == null) { + throw new NullPointerException("Long ID can not be null"); + } + return theIdPart.toString(); + } + @Override public boolean isEmpty() { return isBlank(getValue()); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java index 2bc08de2549..8f513af3020 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java @@ -146,8 +146,9 @@ public class MethodOutcome { * If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist. */ - public void setCreated(Boolean theCreated) { + public MethodOutcome setCreated(Boolean theCreated) { myCreated = theCreated; + return this; } /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java index bf46e32ef0b..6a17c587097 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java @@ -182,9 +182,14 @@ public abstract class BaseClient { } try { - ContentType ct = ContentType.get(response.getEntity()); - String mimeType = ct != null ? ct.getMimeType() : null; - + String mimeType; + if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatusLine().getStatusCode()) { + mimeType = null; + } else { + ContentType ct = ContentType.get(response.getEntity()); + mimeType = ct != null ? ct.getMimeType() : null; + } + Map> headers = new HashMap>(); if (response.getAllHeaders() != null) { for (Header next : response.getAllHeaders()) { @@ -398,7 +403,9 @@ public abstract class BaseClient { charset = ct.getCharset(); } if (charset == null) { - ourLog.warn("Response did not specify a charset."); + if (Constants.STATUS_HTTP_204_NO_CONTENT != theResponse.getStatusLine().getStatusCode()) { + ourLog.warn("Response did not specify a charset."); + } charset = Charset.forName("UTF-8"); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java index f28fdeaba2d..41cd049f4d5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java @@ -59,6 +59,9 @@ public abstract class BaseHttpClientInvocation { public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding); protected static void appendExtraParamsWithQuestionMark(Map> theExtraParams, StringBuilder theUrlBuilder, boolean theWithQuestionMark) { + if (theExtraParams==null) { + return; + } boolean first = theWithQuestionMark; if (theExtraParams != null && theExtraParams.isEmpty() == false) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index 6ffbd24f3a7..4306964d546 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -56,10 +56,14 @@ import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.gclient.IClientExecutable; import ca.uhn.fhir.rest.gclient.ICreate; import ca.uhn.fhir.rest.gclient.ICreateTyped; +import ca.uhn.fhir.rest.gclient.ICreateWithQuery; +import ca.uhn.fhir.rest.gclient.ICreateWithQueryTyped; import ca.uhn.fhir.rest.gclient.ICriterion; import ca.uhn.fhir.rest.gclient.ICriterionInternal; import ca.uhn.fhir.rest.gclient.IDelete; import ca.uhn.fhir.rest.gclient.IDeleteTyped; +import ca.uhn.fhir.rest.gclient.IDeleteWithQuery; +import ca.uhn.fhir.rest.gclient.IDeleteWithQueryTyped; import ca.uhn.fhir.rest.gclient.IGetPage; import ca.uhn.fhir.rest.gclient.IGetPageTyped; import ca.uhn.fhir.rest.gclient.IGetTags; @@ -76,6 +80,8 @@ import ca.uhn.fhir.rest.gclient.IUntypedQuery; import ca.uhn.fhir.rest.gclient.IUpdate; import ca.uhn.fhir.rest.gclient.IUpdateExecutable; import ca.uhn.fhir.rest.gclient.IUpdateTyped; +import ca.uhn.fhir.rest.gclient.IUpdateWithQuery; +import ca.uhn.fhir.rest.gclient.IUpdateWithQueryTyped; import ca.uhn.fhir.rest.method.DeleteMethodBinding; import ca.uhn.fhir.rest.method.HistoryMethodBinding; import ca.uhn.fhir.rest.method.HttpDeleteClientInvocation; @@ -237,10 +243,10 @@ public class GenericClient extends BaseClient implements IGenericClient { return new LoadPageInternal(); } -// @Override -// public T read(final Class theType, IdDt theId) { -// return doReadOrVRead(theType, theId, false, null, null); -// } + // @Override + // public T read(final Class theType, IdDt theId) { + // return doReadOrVRead(theType, theId, false, null, null); + // } @Override public T read(Class theType, String theId) { @@ -249,7 +255,7 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public T read(final Class theType, UriDt theUrl) { - IdDt id = theUrl instanceof IdDt ? ((IdDt)theUrl) : new IdDt(theUrl); + IdDt id = theUrl instanceof IdDt ? ((IdDt) theUrl) : new IdDt(theUrl); return doReadOrVRead(theType, id, false, null, null); } @@ -447,7 +453,7 @@ public class GenericClient extends BaseClient implements IGenericClient { if (theIfVersionMatches != null) { invocation.addHeader(Constants.HEADER_IF_NONE_MATCH, '"' + theIfVersionMatches + '"'); } - + ResourceResponseHandler binding = new ResourceResponseHandler(theType, id); if (theNotModifiedHandler == null) { @@ -475,18 +481,18 @@ public class GenericClient extends BaseClient implements IGenericClient { return vread(theType, resId); } + private static void addParam(Map> params, String parameterName, String parameterValue) { + if (!params.containsKey(parameterName)) { + params.put(parameterName, new ArrayList()); + } + params.get(parameterName).add(parameterValue); + } + private abstract class BaseClientExecutable, Y> implements IClientExecutable { private EncodingEnum myParamEncoding; private Boolean myPrettyPrint; private boolean myQueryLogRequestAndResponse; - protected void addParam(Map> params, String parameterName, String parameterValue) { - if (!params.containsKey(parameterName)) { - params.put(parameterName, new ArrayList()); - } - params.get(parameterName).add(parameterValue); - } - @SuppressWarnings("unchecked") @Override public T andLogRequestAndResponse(boolean theLogRequestAndResponse) { @@ -575,11 +581,13 @@ public class GenericClient extends BaseClient implements IGenericClient { } } - private class CreateInternal extends BaseClientExecutable implements ICreate, ICreateTyped { + private class CreateInternal extends BaseClientExecutable implements ICreate, ICreateTyped, ICreateWithQuery, ICreateWithQueryTyped { private String myId; private IResource myResource; private String myResourceBody; + private String mySearchUrl; + private CriterionList myCriterionList; @Override public MethodOutcome execute() { @@ -593,7 +601,14 @@ public class GenericClient extends BaseClient implements IGenericClient { myResourceBody = null; } - BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext); + BaseHttpClientInvocation invocation; + if (mySearchUrl != null) { + invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext, mySearchUrl); + } else if (myCriterionList != null) { + invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext, myCriterionList.toParamList()); + } else { + invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext); + } RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); final String resourceName = def.getName(); @@ -631,15 +646,50 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public ICreateTyped conditionalByUrl(String theSearchUrl) { + mySearchUrl = theSearchUrl; + return this; + } + + @Override + public ICreateWithQuery conditional() { + myCriterionList = new CriterionList(); + return this; + } + + @Override + public ICreateWithQueryTyped where(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + + @Override + public ICreateWithQueryTyped and(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + } - private class DeleteInternal extends BaseClientExecutable implements IDelete, IDeleteTyped { + private class DeleteInternal extends BaseClientExecutable implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped { private IdDt myId; + private String mySearchUrl; + private String myResourceType; + private CriterionList myCriterionList; @Override public BaseOperationOutcome execute() { - HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(myId); + HttpDeleteClientInvocation invocation; + if (myId != null) { + invocation = DeleteMethodBinding.createDeleteInvocation(myId); + } else if (myCriterionList != null) { + Map> params = myCriterionList.toParamList(); + invocation = DeleteMethodBinding.createDeleteInvocation(myResourceType, params); + } else { + invocation = DeleteMethodBinding.createDeleteInvocation(mySearchUrl); + } OperationOutcomeResponseHandler binding = new OperationOutcomeResponseHandler(); Map> params = new HashMap>(); return invoke(params, binding, invocation); @@ -680,6 +730,56 @@ public class GenericClient extends BaseClient implements IGenericClient { myId = new IdDt(theResourceType, theLogicalId); return this; } + + @Override + public IDeleteTyped resourceConditionalByUrl(String theSearchUrl) { + Validate.notBlank(theSearchUrl, "theSearchUrl can not be blank/null"); + mySearchUrl = theSearchUrl; + return this; + } + + @Override + public IDeleteWithQuery resourceConditionalByType(String theResourceType) { + Validate.notBlank(theResourceType, "theResourceType can not be blank/null"); + if (myContext.getResourceDefinition(theResourceType) == null) { + throw new IllegalArgumentException("Unknown resource type: " + theResourceType); + } + myResourceType = theResourceType; + myCriterionList = new CriterionList(); + return this; + } + + @Override + public IDeleteWithQueryTyped where(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + + @Override + public IDeleteWithQueryTyped and(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + } + + private static class CriterionList extends ArrayList { + + private static final long serialVersionUID = 1L; + + public void populateParamList(Map> theParams) { + for (ICriterionInternal next : this) { + String parameterName = next.getParameterName(); + String parameterValue = next.getParameterValue(); + addParam(theParams, parameterName, parameterValue); + } + } + + public Map> toParamList() { + LinkedHashMap> retVal = new LinkedHashMap>(); + populateParamList(retVal); + return retVal; + } + } private class GetPageInternal extends BaseClientExecutable implements IGetPageTyped { @@ -996,7 +1096,7 @@ public class GenericClient extends BaseClient implements IGenericClient { private class SearchInternal extends BaseClientExecutable implements IQuery, IUntypedQuery { private String myCompartmentName; - private List myCriterion = new ArrayList(); + private CriterionList myCriterion = new CriterionList(); private List myInclude = new ArrayList(); private Integer myParamLimit; private String myResourceId; @@ -1025,11 +1125,7 @@ public class GenericClient extends BaseClient implements IGenericClient { // params.putAll(initial); // } - for (ICriterionInternal next : myCriterion) { - String parameterName = next.getParameterName(); - String parameterValue = next.getParameterValue(); - addParam(params, parameterName, parameterValue); - } + myCriterion.populateParamList(params); for (Include next : myInclude) { addParam(params, Constants.PARAM_INCLUDE, next.getValue()); @@ -1224,30 +1320,39 @@ public class GenericClient extends BaseClient implements IGenericClient { } - private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped, IUpdateExecutable { + private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped { private IdDt myId; private IResource myResource; private String myResourceBody; + private String mySearchUrl; + private CriterionList myCriterionList; @Override public MethodOutcome execute() { if (myResource == null) { myResource = parseResourceBody(myResourceBody); } - if (myId == null) { - myId = myResource.getId(); - } - if (myId == null || myId.hasIdPart() == false) { - throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server"); - } // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding if (getParamEncoding() != null) { myResourceBody = null; } - BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); + BaseHttpClientInvocation invocation; + if (mySearchUrl != null) { + invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, mySearchUrl); + } else if (myCriterionList != null) { + invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, myCriterionList.toParamList()); + } else { + if (myId == null) { + myId = myResource.getId(); + } + if (myId == null || myId.hasIdPart() == false) { + throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server"); + } + invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); + } RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); final String resourceName = def.getName(); @@ -1297,6 +1402,30 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public IUpdateTyped conditionalByUrl(String theSearchUrl) { + mySearchUrl = theSearchUrl; + return this; + } + + @Override + public IUpdateWithQuery conditional() { + myCriterionList = new CriterionList(); + return this; + } + + @Override + public IUpdateWithQueryTyped where(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + + @Override + public IUpdateWithQueryTyped and(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseQuery.java new file mode 100644 index 00000000000..8084e5e204c --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseQuery.java @@ -0,0 +1,9 @@ +package ca.uhn.fhir.rest.gclient; + +public interface IBaseQuery { + + T where(ICriterion theCriterion); + + T and(ICriterion theCriterion); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java index 59516fffe3f..9bea778502a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java @@ -37,4 +37,20 @@ public interface ICreateTyped extends IClientExecutable[ResourceType]?[Parameters], + * for example: Patient?name=Smith&identifier=13.2.4.11.4%7C847366 + * + * @since HAPI 0.9 / FHIR DSTU 2 + */ + ICreateTyped conditionalByUrl(String theSearchUrl); + + /** + * @since HAPI 0.9 / FHIR DSTU 2 + */ + ICreateWithQuery conditional(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQuery.java new file mode 100644 index 00000000000..4b52e8818d8 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQuery.java @@ -0,0 +1,6 @@ +package ca.uhn.fhir.rest.gclient; + + +public interface ICreateWithQuery extends IBaseQuery { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQueryTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQueryTyped.java new file mode 100644 index 00000000000..a0ca9af9843 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQueryTyped.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.rest.gclient; + +public interface ICreateWithQueryTyped extends ICreateTyped, ICreateWithQuery { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDelete.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDelete.java index 6c6106d49b2..866c7652ef8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDelete.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDelete.java @@ -30,5 +30,21 @@ public interface IDelete { IDeleteTyped resourceById(IdDt theId); IDeleteTyped resourceById(String theResourceType, String theLogicalId); - + + /** + * Specifies that the delete should be performed as a conditional delete + * against a given search URL. + * + * @param theSearchUrl The search URL to use. The format of this URL should be of the form [ResourceType]?[Parameters], + * for example: Patient?name=Smith&identifier=13.2.4.11.4%7C847366 + * + * @since HAPI 0.9 / FHIR DSTU 2 + */ + IDeleteTyped resourceConditionalByUrl(String theSearchUrl); + + /** + * @since HAPI 0.9 / FHIR DSTU 2 + */ + IDeleteWithQuery resourceConditionalByType(String theResourceType); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQuery.java new file mode 100644 index 00000000000..060b0cdb077 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQuery.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.rest.gclient; + +public interface IDeleteWithQuery extends IBaseQuery { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQueryTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQueryTyped.java new file mode 100644 index 00000000000..83e04f3ca50 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQueryTyped.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.rest.gclient; + +public interface IDeleteWithQueryTyped extends IDeleteTyped, IDeleteWithQuery { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java index 520befc4e3d..0e14d5cda70 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java @@ -24,11 +24,7 @@ import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.method.SearchStyleEnum; -public interface IQuery extends IClientExecutable { - - IQuery where(ICriterion theCriterion); - - IQuery and(ICriterion theCriterion); +public interface IQuery extends IClientExecutable, IBaseQuery { IQuery include(Include theIncludeManagingorganization); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java index 0cd0693c990..e7f98612732 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java @@ -28,4 +28,20 @@ public interface IUpdateTyped extends IUpdateExecutable { IUpdateExecutable withId(String theId); + /** + * Specifies that the update should be performed as a conditional create + * against a given search URL. + * + * @param theSearchUrl The search URL to use. The format of this URL should be of the form [ResourceType]?[Parameters], + * for example: Patient?name=Smith&identifier=13.2.4.11.4%7C847366 + * + * @since HAPI 0.9 / FHIR DSTU 2 + */ + IUpdateTyped conditionalByUrl(String theSearchUrl); + + /** + * @since HAPI 0.9 / FHIR DSTU 2 + */ + IUpdateWithQuery conditional(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQuery.java new file mode 100644 index 00000000000..2a6380cacd3 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQuery.java @@ -0,0 +1,6 @@ +package ca.uhn.fhir.rest.gclient; + + +public interface IUpdateWithQuery extends IBaseQuery { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQueryTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQueryTyped.java new file mode 100644 index 00000000000..cb61b6786c4 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQueryTyped.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.rest.gclient; + +public interface IUpdateWithQueryTyped extends IUpdateTyped, IUpdateWithQuery { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java index 1b3c0de5393..2fd3533802b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java @@ -143,29 +143,55 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myBundleType = null; } + public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, Map> theParams, String... theUrlPath) { + myContext = theContext; + myResource = null; + myTagList = null; + myUrlPath = StringUtils.join(theUrlPath, '/'); + myResources = null; + myBundle = null; + myContents = theContents; + myContentsIsBundle = false; + myParams = theParams; + myBundleType = null; + } + + public BaseHttpClientInvocationWithContents(FhirContext theContext, IResource theResource, Map> theParams, String... theUrlPath) { + myContext = theContext; + myResource = theResource; + myTagList = null; + myUrlPath = StringUtils.join(theUrlPath, '/'); + myResources = null; + myBundle = null; + myContents = null; + myContentsIsBundle = false; + myParams = theParams; + myBundleType = null; + } + @Override public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding) throws DataFormatException { - StringBuilder b = new StringBuilder(); + StringBuilder url = new StringBuilder(); if (myUrlPath == null) { - b.append(theUrlBase); + url.append(theUrlBase); } else { if (!myUrlPath.contains("://")) { - b.append(theUrlBase); + url.append(theUrlBase); if (!theUrlBase.endsWith("/")) { - b.append('/'); + url.append('/'); } } - b.append(myUrlPath); + url.append(myUrlPath); } - appendExtraParamsWithQuestionMark(theExtraParams, b, b.indexOf("?") == -1); - String url = b.toString(); + appendExtraParamsWithQuestionMark(theExtraParams, url, url.indexOf("?") == -1); if (myResource != null && BaseBinary.class.isAssignableFrom(myResource.getClass())) { BaseBinary binary = (BaseBinary) myResource; ByteArrayEntity entity = new ByteArrayEntity(binary.getContent(), ContentType.parse(binary.getContentType())); HttpRequestBase retVal = createRequest(url, entity); + addMatchHeaders(retVal, url); return retVal; } @@ -177,7 +203,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca if (myContents != null) { encoding = MethodUtil.detectEncoding(myContents); } - + if (encoding == EncodingEnum.JSON) { parser = myContext.newJsonParser(); } else { @@ -187,12 +213,12 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca AbstractHttpEntity entity; if (myParams != null) { - contentType= null; + contentType = null; List parameters = new ArrayList(); for (Entry> nextParam : myParams.entrySet()) { List value = nextParam.getValue(); - for(String s: value){ - parameters.add(new BasicNameValuePair(nextParam.getKey(), s)); + for (String s : value) { + parameters.add(new BasicNameValuePair(nextParam.getKey(), s)); } } try { @@ -228,6 +254,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca HttpRequestBase retVal = createRequest(url, entity); super.addHeadersToRequest(retVal); + addMatchHeaders(retVal, url); if (contentType != null) { retVal.addHeader(Constants.HEADER_CONTENT_TYPE, contentType + Constants.HEADER_SUFFIX_CT_UTF_8); @@ -236,6 +263,41 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca return retVal; } - protected abstract HttpRequestBase createRequest(String url, AbstractHttpEntity theEntity); + private void addMatchHeaders(HttpRequestBase theHttpRequest, StringBuilder theUrlBase) { + if (myIfNoneExistParams != null) { + StringBuilder b = newHeaderBuilder(theUrlBase); + appendExtraParamsWithQuestionMark(myIfNoneExistParams, b, b.indexOf("?") == -1); + theHttpRequest.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); + } + + if (myIfNoneExistString != null) { + StringBuilder b = newHeaderBuilder(theUrlBase); + b.append(b.indexOf("?") == -1 ? '?' : '&'); + b.append(myIfNoneExistString.substring(myIfNoneExistString.indexOf('?') + 1)); + theHttpRequest.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); + } + } + + private StringBuilder newHeaderBuilder(StringBuilder theUrlBase) { + StringBuilder b = new StringBuilder(); + b.append(theUrlBase); + if (theUrlBase.length() > 0 && theUrlBase.charAt(theUrlBase.length() - 1) == '/') { + b.deleteCharAt(b.length() - 1); + } + return b; + } + + protected abstract HttpRequestBase createRequest(StringBuilder theUrl, AbstractHttpEntity theEntity); + + private Map> myIfNoneExistParams; + private String myIfNoneExistString; + + public void setIfNoneExistParams(Map> theIfNoneExist) { + myIfNoneExistParams = theIfNoneExist; + } + + public void setIfNoneExistString(String theIfNoneExistString) { + myIfNoneExistString = theIfNoneExistString; + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java index 4a01f4c87e1..ee07daf5205 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java @@ -22,6 +22,8 @@ package ca.uhn.fhir.rest.method; import java.lang.reflect.Method; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Set; import ca.uhn.fhir.context.ConfigurationException; @@ -154,5 +156,14 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding { return null; } + public static HttpDeleteClientInvocation createDeleteInvocation(String theSearchUrl) { + HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theSearchUrl); + return retVal; + } + + public static HttpDeleteClientInvocation createDeleteInvocation(String theResourceType, Map> theParams) { + return new HttpDeleteClientInvocation(theResourceType, theParams); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java index cc14fc917a0..3bee2d4770e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java @@ -33,12 +33,22 @@ import ca.uhn.fhir.rest.server.EncodingEnum; public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { private String myUrlPath; + private Map> myParams; public HttpDeleteClientInvocation(IdDt theId) { super(); myUrlPath = theId.toUnqualifiedVersionless().getValue(); } + public HttpDeleteClientInvocation(String theSearchUrl) { + myUrlPath = theSearchUrl; + } + + public HttpDeleteClientInvocation(String theResourceType, Map> theParams) { + myUrlPath = theResourceType; + myParams = theParams; + } + @Override public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding) { StringBuilder b = new StringBuilder(); @@ -48,12 +58,12 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { } b.append(myUrlPath); - appendExtraParamsWithQuestionMark(theExtraParams, b,b.indexOf("?") == -1); + appendExtraParamsWithQuestionMark(myParams, b, b.indexOf("?") == -1); + appendExtraParamsWithQuestionMark(theExtraParams, b, b.indexOf("?") == -1); HttpDelete retVal = new HttpDelete(b.toString()); super.addHeadersToRequest(retVal); return retVal; } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java index 97a8a152eaf..8dcb990f43e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java @@ -34,38 +34,34 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum; public class HttpPostClientInvocation extends BaseHttpClientInvocationWithContents { + public HttpPostClientInvocation(FhirContext theContext, IResource theResource, String theUrlExtension) { super(theContext, theResource, theUrlExtension); } - public HttpPostClientInvocation(FhirContext theContext, TagList theTagList, String... theUrlExtension) { super(theContext, theTagList, theUrlExtension); } - public HttpPostClientInvocation(FhirContext theContext, List theResources, BundleTypeEnum theBundleType) { super(theContext, theResources, theBundleType); } - public HttpPostClientInvocation(FhirContext theContext, Bundle theBundle) { - super(theContext,theBundle); + super(theContext, theBundle); } public HttpPostClientInvocation(FhirContext theContext, String theContents, boolean theIsBundle, String theUrlExtension) { - super(theContext,theContents, theIsBundle, theUrlExtension); + super(theContext, theContents, theIsBundle, theUrlExtension); } - public HttpPostClientInvocation(FhirContext theContext, Map> theParams, String... theUrlExtension) { super(theContext, theParams, theUrlExtension); } - @Override - protected HttpPost createRequest(String url, AbstractHttpEntity theEntity) { - HttpPost retVal = new HttpPost(url); + protected HttpPost createRequest(StringBuilder theUrlBase, AbstractHttpEntity theEntity) { + HttpPost retVal = new HttpPost(theUrlBase.toString()); retVal.setEntity(theEntity); return retVal; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java index 68822ca5c0b..e9f351c1100 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java @@ -34,15 +34,14 @@ public class HttpPutClientInvocation extends BaseHttpClientInvocationWithContent } public HttpPutClientInvocation(FhirContext theContext, String theContents, boolean theIsBundle, String theUrlExtension) { - super(theContext,theContents, theIsBundle, theUrlExtension); + super(theContext, theContents, theIsBundle, theUrlExtension); } @Override - protected HttpRequestBase createRequest(String url, AbstractHttpEntity theEntity) { - HttpPut retVal = new HttpPut(url); + protected HttpRequestBase createRequest(StringBuilder theUrl, AbstractHttpEntity theEntity) { + HttpPut retVal = new HttpPut(theUrl.toString()); retVal.setEntity(theEntity); return retVal; } - } 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 5b5193b20d9..37c8d260672 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 @@ -5,14 +5,17 @@ import static org.apache.commons.lang3.StringUtils.*; import java.io.IOException; import java.io.PushbackReader; import java.io.Reader; +import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -95,9 +98,65 @@ import ca.uhn.fhir.util.ReflectionUtil; public class MethodUtil { 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 org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class); + static void addTagsToPostOrPut(IResource resource, BaseHttpClientInvocation retVal) { + TagList list = (TagList) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); + if (list != null) { + for (Tag tag : list) { + if (StringUtils.isNotBlank(tag.getTerm())) { + retVal.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); + } + } + } + } + + public static HttpGetClientInvocation createConformanceInvocation() { + return new HttpGetClientInvocation("metadata"); + } + + public static HttpPostClientInvocation createCreateInvocation(IResource theResource, FhirContext theContext) { + return createCreateInvocation(theResource, null, null, theContext); + } + + public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext) { + RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); + String resourceName = def.getName(); + + StringBuilder urlExtension = new StringBuilder(); + urlExtension.append(resourceName); + + if (StringUtils.isNotBlank(theId)) { + urlExtension.append('/'); + urlExtension.append(theId); + } + + HttpPostClientInvocation retVal; + if (StringUtils.isBlank(theResourceBody)) { + retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString()); + } else { + retVal = new HttpPostClientInvocation(theContext, theResourceBody, false, urlExtension.toString()); + } + addTagsToPostOrPut(theResource, retVal); + + // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody); + + return retVal; + } + + public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext, Map> theIfNoneExistParams) { + HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext); + retVal.setIfNoneExistParams(theIfNoneExistParams); + return retVal; + } + + public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext, String theIfNoneExistUrl) { + HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext); + retVal.setIfNoneExistString(theIfNoneExistUrl); + return retVal; + } public static HttpPutClientInvocation createUpdateInvocation(IResource theResource, String theResourceBody, IdDt theId, FhirContext theContext) { String resourceName = theContext.getResourceDefinition(theResource).getName(); @@ -105,9 +164,9 @@ public class MethodUtil { urlBuilder.append(resourceName); urlBuilder.append('/'); urlBuilder.append(theId.getIdPart()); + String urlExtension = urlBuilder.toString(); HttpPutClientInvocation retVal; - String urlExtension = urlBuilder.toString(); if (StringUtils.isBlank(theResourceBody)) { retVal = new HttpPutClientInvocation(theContext, theResource, urlExtension); } else { @@ -135,203 +194,49 @@ public class MethodUtil { return retVal; } - public static void parseClientRequestResourceHeaders(IdDt theRequestedId, Map> theHeaders, IBaseResource resource) { - List lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE); - if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) { - String headerValue = lmHeaders.get(0); - Date headerDateValue; - try { - headerDateValue = DateUtils.parseDate(headerValue); - if (resource instanceof IResource) { - InstantDt lmValue = new InstantDt(headerDateValue); - ((IResource) resource).getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue); - } else if (resource instanceof IAnyResource) { - ((IAnyResource) resource).getMeta().setLastUpdated(headerDateValue); - } - } catch (Exception e) { - ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString()); - } - } - - List clHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); - if (clHeaders != null && clHeaders.size() > 0 && StringUtils.isNotBlank(clHeaders.get(0))) { - String headerValue = clHeaders.get(0); - if (isNotBlank(headerValue)) { - new IdDt(headerValue).applyTo(resource); - } - } - - IdDt existing = IdDt.of(resource); - - List eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC); - String eTagVersion = null; - if (eTagHeaders != null && eTagHeaders.size() > 0) { - eTagVersion = parseETagValue(eTagHeaders.get(0)); - } - if (isNotBlank(eTagVersion)) { - if (existing == null || existing.isEmpty()) { - if (theRequestedId != null) { - theRequestedId.withVersion(eTagVersion).applyTo(resource); - } - } else if (existing.hasVersionIdPart() == false) { - existing.withVersion(eTagVersion).applyTo(resource); - } - } else if (existing == null || existing.isEmpty()) { - if (theRequestedId != null) { - theRequestedId.applyTo(resource); - } - } - - List categoryHeaders = theHeaders.get(Constants.HEADER_CATEGORY_LC); - if (categoryHeaders != null && categoryHeaders.size() > 0 && StringUtils.isNotBlank(categoryHeaders.get(0))) { - TagList tagList = new TagList(); - for (String header : categoryHeaders) { - parseTagValue(tagList, header); - } - if (resource instanceof IResource) { - ResourceMetadataKeyEnum.TAG_LIST.put((IResource) resource, tagList); - } else if (resource instanceof IAnyResource) { - IMetaType meta = ((IAnyResource) resource).getMeta(); - for (Tag next : tagList) { - meta.addTag().setSystem(next.getScheme()).setCode(next.getTerm()).setDisplay(next.getLabel()); + public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IResource theResource, String theResourceBody, Map> theMatchParams) { + StringBuilder b = new StringBuilder(); + + String resourceType = theContext.getResourceDefinition(theResource).getName(); + b.append(resourceType); + + boolean haveQuestionMark=false; + for (Entry> nextEntry : theMatchParams.entrySet()) { + for (String nextValue : nextEntry.getValue()) { + b.append(haveQuestionMark ? '&' : '?'); + haveQuestionMark = true; + try { + b.append(URLEncoder.encode(nextEntry.getKey(), "UTF-8")); + b.append('='); + b.append(URLEncoder.encode(nextValue, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new ConfigurationException("UTF-8 not supported on this platform"); } } } - } - public static String parseETagValue(String value) { - String eTagVersion; - value = value.trim(); - if (value.length() > 1) { - if (value.charAt(value.length() - 1) == '"') { - if (value.charAt(0) == '"') { - eTagVersion = value.substring(1, value.length() - 1); - } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' && value.charAt(2) == '"') { - eTagVersion = value.substring(3, value.length() - 1); - } else { - eTagVersion = value; - } - } else { - eTagVersion = value; - } - } else { - eTagVersion = value; - } - return eTagVersion; - } - - public static void parseTagValue(TagList tagList, String nextTagComplete) { - StringBuilder next = new StringBuilder(nextTagComplete); - parseTagValue(tagList, nextTagComplete, next); - } - - private static void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) { - int firstSemicolon = theBuffer.indexOf(";"); - int deleteTo; - if (firstSemicolon == -1) { - firstSemicolon = theBuffer.indexOf(","); - if (firstSemicolon == -1) { - firstSemicolon = theBuffer.length(); - deleteTo = theBuffer.length(); - } else { - deleteTo = firstSemicolon; - } - } else { - deleteTo = firstSemicolon + 1; - } - - String term = theBuffer.substring(0, firstSemicolon); - String scheme = null; - String label = null; - if (isBlank(term)) { - return; - } - - theBuffer.delete(0, deleteTo); - while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { - theBuffer.deleteCharAt(0); - } - - while (theBuffer.length() > 0) { - boolean foundSomething = false; - if (theBuffer.length() > SCHEME.length() && theBuffer.substring(0, SCHEME.length()).equals(SCHEME)) { - int closeIdx = theBuffer.indexOf("\"", SCHEME.length()); - scheme = theBuffer.substring(SCHEME.length(), closeIdx); - theBuffer.delete(0, closeIdx + 1); - foundSomething = true; - } - if (theBuffer.length() > LABEL.length() && theBuffer.substring(0, LABEL.length()).equals(LABEL)) { - int closeIdx = theBuffer.indexOf("\"", LABEL.length()); - label = theBuffer.substring(LABEL.length(), closeIdx); - theBuffer.delete(0, closeIdx + 1); - foundSomething = true; - } - // TODO: support enc2231-string as described in - // http://tools.ietf.org/html/draft-johnston-http-category-header-02 - // TODO: support multiple tags in one header as described in - // http://hl7.org/implement/standards/fhir/http.html#tags - - while (theBuffer.length() > 0 && (theBuffer.charAt(0) == ' ' || theBuffer.charAt(0) == ';')) { - theBuffer.deleteCharAt(0); - } - - if (!foundSomething) { - break; - } - } - - if (theBuffer.length() > 0 && theBuffer.charAt(0) == ',') { - theBuffer.deleteCharAt(0); - while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { - theBuffer.deleteCharAt(0); - } - theTagList.add(new Tag(scheme, term, label)); - parseTagValue(theTagList, theCompleteHeaderValue, theBuffer); - } else { - theTagList.add(new Tag(scheme, term, label)); - } - - if (theBuffer.length() > 0) { - ourLog.warn("Ignoring extra text at the end of " + Constants.HEADER_CATEGORY + " tag '" + theBuffer.toString() + "' - Complete tag value was: " + theCompleteHeaderValue); - } - - } - - static void addTagsToPostOrPut(IResource resource, BaseHttpClientInvocation retVal) { - TagList list = (TagList) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); - if (list != null) { - for (Tag tag : list) { - if (StringUtils.isNotBlank(tag.getTerm())) { - retVal.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); - } - } - } - } - - public static HttpPostClientInvocation createCreateInvocation(IResource theResource, FhirContext theContext) { - return createCreateInvocation(theResource, null, null, theContext); - } - - public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext) { - RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); - String resourceName = def.getName(); - - StringBuilder urlExtension = new StringBuilder(); - urlExtension.append(resourceName); - if (StringUtils.isNotBlank(theId)) { - urlExtension.append('/'); - urlExtension.append(theId); - } - - HttpPostClientInvocation retVal; + + HttpPutClientInvocation retVal; if (StringUtils.isBlank(theResourceBody)) { - retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString()); + retVal = new HttpPutClientInvocation(theContext, theResource, b.toString()); } else { - retVal = new HttpPostClientInvocation(theContext, theResourceBody, false, urlExtension.toString()); + retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, b.toString()); } + addTagsToPostOrPut(theResource, retVal); - // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody); + return retVal; + } + + public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IResource theResource, String theResourceBody, String theMatchUrl) { + HttpPutClientInvocation retVal; + if (StringUtils.isBlank(theResourceBody)) { + retVal = new HttpPutClientInvocation(theContext, theResource, theMatchUrl); + } else { + retVal = new HttpPutClientInvocation(theContext, theResourceBody, false,theMatchUrl); + } + + addTagsToPostOrPut(theResource, retVal); return retVal; } @@ -348,57 +253,21 @@ public class MethodUtil { return EncodingEnum.XML; } - public static HttpGetClientInvocation createConformanceInvocation() { - return new HttpGetClientInvocation("metadata"); - } - - public static MethodOutcome process2xxResponse(FhirContext theContext, String theResourceName, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map> theHeaders) { - List locationHeaders = new ArrayList(); - List lh = theHeaders.get(Constants.HEADER_LOCATION_LC); - if (lh != null) { - locationHeaders.addAll(lh); - } - List clh = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); - if (clh != null) { - locationHeaders.addAll(clh); - } - - MethodOutcome retVal = new MethodOutcome(); - if (locationHeaders != null && locationHeaders.size() > 0) { - String locationHeader = locationHeaders.get(0); - BaseOutcomeReturningMethodBinding.parseContentLocation(retVal, theResourceName, locationHeader); - } - if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) { - EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType); - if (ct != null) { - PushbackReader reader = new PushbackReader(theResponseReader); - - try { - int firstByte = reader.read(); - if (firstByte == -1) { - BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read"); - reader = null; - } else { - reader.unread(firstByte); - } - } catch (IOException e) { - BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read", e); - reader = null; + public static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) { + for (Annotation annotation : theAnnotations) { + if (annotation instanceof Description) { + Description desc = (Description) annotation; + if (isNotBlank(desc.formalDefinition())) { + theParameter.setDescription(desc.formalDefinition()); + } else { + theParameter.setDescription(desc.shortDefinition()); } - - if (reader != null) { - IParser parser = ct.newParser(theContext); - IResource outcome = parser.parseResource(reader); - if (outcome instanceof BaseOperationOutcome) { - retVal.setOperationOutcome((BaseOperationOutcome) outcome); - } - } - - } else { - BaseOutcomeReturningMethodBinding.ourLog.debug("Ignoring response content of type: {}", theResponseMimeType); } } - return retVal; + } + + public static Integer findIdParameterIndex(Method theMethod) { + return MethodUtil.findParamAnnotationIndex(theMethod, IdParam.class); } public static Integer findParamAnnotationIndex(Method theMethod, Class toFind) { @@ -416,38 +285,8 @@ public class MethodUtil { return null; } - public static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) { - for (Annotation annotation : theAnnotations) { - if (annotation instanceof Description) { - Description desc = (Description) annotation; - if (isNotBlank(desc.formalDefinition())) { - theParameter.setDescription(desc.formalDefinition()); - } else { - theParameter.setDescription(desc.shortDefinition()); - } - } - } - } - - public static IQueryParameterOr singleton(final IQueryParameterType theParam) { - return new IQueryParameterOr() { - - @Override - public void setValuesAsQueryTokens(QualifiedParamList theParameters) { - if (theParameters.isEmpty()) { - return; - } - if (theParameters.size() > 1) { - throw new IllegalArgumentException("Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); - } - theParam.setValueAsQueryToken(theParameters.getQualifier(), theParameters.get(0)); - } - - @Override - public List getValuesAsQueryTokens() { - return Collections.singletonList(theParam); - } - }; + public static Integer findTagListParameterIndex(Method theMethod) { + return MethodUtil.findParamAnnotationIndex(theMethod, TagListParam.class); } @SuppressWarnings("deprecation") @@ -455,46 +294,6 @@ public class MethodUtil { return MethodUtil.findParamAnnotationIndex(theMethod, VersionIdParam.class); } - public static Integer findIdParameterIndex(Method theMethod) { - return MethodUtil.findParamAnnotationIndex(theMethod, IdParam.class); - } - - public static Integer findTagListParameterIndex(Method theMethod) { - return MethodUtil.findParamAnnotationIndex(theMethod, TagListParam.class); - } - - /** - * This is a utility method intended provided to help the JPA module. - */ - public static IQueryParameterAnd parseQueryParams(RuntimeSearchParam theParamDef, String theUnqualifiedParamName, List theParameters) { - QueryParameterAndBinder binder = null; - switch (theParamDef.getParamType()) { - case COMPOSITE: - throw new UnsupportedOperationException(); - case DATE: - binder = new QueryParameterAndBinder(DateAndListParam.class, Collections.>emptyList()); - break; - case NUMBER: - binder = new QueryParameterAndBinder(NumberAndListParam.class, Collections.>emptyList()); - break; - case QUANTITY: - binder = new QueryParameterAndBinder(QuantityAndListParam.class, Collections.>emptyList()); - break; - case REFERENCE: - binder = new QueryParameterAndBinder(ReferenceAndListParam.class, Collections.>emptyList()); - break; - case STRING: - binder = new QueryParameterAndBinder(StringAndListParam.class, Collections.>emptyList()); - break; - case TOKEN: - binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections.>emptyList()); - break; - } - - return binder.parse(theUnqualifiedParamName, theParameters); - } - - @SuppressWarnings("unchecked") public static List getResourceParameters(FhirContext theContext, Method theMethod, Object theProvider) { List parameters = new ArrayList(); @@ -612,4 +411,267 @@ public class MethodUtil { return parameters; } + public static void parseClientRequestResourceHeaders(IdDt theRequestedId, Map> theHeaders, IBaseResource resource) { + List lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE); + if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) { + String headerValue = lmHeaders.get(0); + Date headerDateValue; + try { + headerDateValue = DateUtils.parseDate(headerValue); + if (resource instanceof IResource) { + InstantDt lmValue = new InstantDt(headerDateValue); + ((IResource) resource).getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue); + } else if (resource instanceof IAnyResource) { + ((IAnyResource) resource).getMeta().setLastUpdated(headerDateValue); + } + } catch (Exception e) { + ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString()); + } + } + + List clHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); + if (clHeaders != null && clHeaders.size() > 0 && StringUtils.isNotBlank(clHeaders.get(0))) { + String headerValue = clHeaders.get(0); + if (isNotBlank(headerValue)) { + new IdDt(headerValue).applyTo(resource); + } + } + + IdDt existing = IdDt.of(resource); + + List eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC); + String eTagVersion = null; + if (eTagHeaders != null && eTagHeaders.size() > 0) { + eTagVersion = parseETagValue(eTagHeaders.get(0)); + } + if (isNotBlank(eTagVersion)) { + if (existing == null || existing.isEmpty()) { + if (theRequestedId != null) { + theRequestedId.withVersion(eTagVersion).applyTo(resource); + } + } else if (existing.hasVersionIdPart() == false) { + existing.withVersion(eTagVersion).applyTo(resource); + } + } else if (existing == null || existing.isEmpty()) { + if (theRequestedId != null) { + theRequestedId.applyTo(resource); + } + } + + List categoryHeaders = theHeaders.get(Constants.HEADER_CATEGORY_LC); + if (categoryHeaders != null && categoryHeaders.size() > 0 && StringUtils.isNotBlank(categoryHeaders.get(0))) { + TagList tagList = new TagList(); + for (String header : categoryHeaders) { + parseTagValue(tagList, header); + } + if (resource instanceof IResource) { + ResourceMetadataKeyEnum.TAG_LIST.put((IResource) resource, tagList); + } else if (resource instanceof IAnyResource) { + IMetaType meta = ((IAnyResource) resource).getMeta(); + for (Tag next : tagList) { + meta.addTag().setSystem(next.getScheme()).setCode(next.getTerm()).setDisplay(next.getLabel()); + } + } + } + } + + public static String parseETagValue(String value) { + String eTagVersion; + value = value.trim(); + if (value.length() > 1) { + if (value.charAt(value.length() - 1) == '"') { + if (value.charAt(0) == '"') { + eTagVersion = value.substring(1, value.length() - 1); + } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' && value.charAt(2) == '"') { + eTagVersion = value.substring(3, value.length() - 1); + } else { + eTagVersion = value; + } + } else { + eTagVersion = value; + } + } else { + eTagVersion = value; + } + return eTagVersion; + } + + /** + * This is a utility method intended provided to help the JPA module. + */ + public static IQueryParameterAnd parseQueryParams(RuntimeSearchParam theParamDef, String theUnqualifiedParamName, List theParameters) { + QueryParameterAndBinder binder = null; + switch (theParamDef.getParamType()) { + case COMPOSITE: + throw new UnsupportedOperationException(); + case DATE: + binder = new QueryParameterAndBinder(DateAndListParam.class, Collections.> emptyList()); + break; + case NUMBER: + binder = new QueryParameterAndBinder(NumberAndListParam.class, Collections.> emptyList()); + break; + case QUANTITY: + binder = new QueryParameterAndBinder(QuantityAndListParam.class, Collections.> emptyList()); + break; + case REFERENCE: + binder = new QueryParameterAndBinder(ReferenceAndListParam.class, Collections.> emptyList()); + break; + case STRING: + binder = new QueryParameterAndBinder(StringAndListParam.class, Collections.> emptyList()); + break; + case TOKEN: + binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections.> emptyList()); + break; + } + + return binder.parse(theUnqualifiedParamName, theParameters); + } + + public static void parseTagValue(TagList tagList, String nextTagComplete) { + StringBuilder next = new StringBuilder(nextTagComplete); + parseTagValue(tagList, nextTagComplete, next); + } + + private static void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) { + int firstSemicolon = theBuffer.indexOf(";"); + int deleteTo; + if (firstSemicolon == -1) { + firstSemicolon = theBuffer.indexOf(","); + if (firstSemicolon == -1) { + firstSemicolon = theBuffer.length(); + deleteTo = theBuffer.length(); + } else { + deleteTo = firstSemicolon; + } + } else { + deleteTo = firstSemicolon + 1; + } + + String term = theBuffer.substring(0, firstSemicolon); + String scheme = null; + String label = null; + if (isBlank(term)) { + return; + } + + theBuffer.delete(0, deleteTo); + while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { + theBuffer.deleteCharAt(0); + } + + while (theBuffer.length() > 0) { + boolean foundSomething = false; + if (theBuffer.length() > SCHEME.length() && theBuffer.substring(0, SCHEME.length()).equals(SCHEME)) { + int closeIdx = theBuffer.indexOf("\"", SCHEME.length()); + scheme = theBuffer.substring(SCHEME.length(), closeIdx); + theBuffer.delete(0, closeIdx + 1); + foundSomething = true; + } + if (theBuffer.length() > LABEL.length() && theBuffer.substring(0, LABEL.length()).equals(LABEL)) { + int closeIdx = theBuffer.indexOf("\"", LABEL.length()); + label = theBuffer.substring(LABEL.length(), closeIdx); + theBuffer.delete(0, closeIdx + 1); + foundSomething = true; + } + // TODO: support enc2231-string as described in + // http://tools.ietf.org/html/draft-johnston-http-category-header-02 + // TODO: support multiple tags in one header as described in + // http://hl7.org/implement/standards/fhir/http.html#tags + + while (theBuffer.length() > 0 && (theBuffer.charAt(0) == ' ' || theBuffer.charAt(0) == ';')) { + theBuffer.deleteCharAt(0); + } + + if (!foundSomething) { + break; + } + } + + if (theBuffer.length() > 0 && theBuffer.charAt(0) == ',') { + theBuffer.deleteCharAt(0); + while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { + theBuffer.deleteCharAt(0); + } + theTagList.add(new Tag(scheme, term, label)); + parseTagValue(theTagList, theCompleteHeaderValue, theBuffer); + } else { + theTagList.add(new Tag(scheme, term, label)); + } + + if (theBuffer.length() > 0) { + ourLog.warn("Ignoring extra text at the end of " + Constants.HEADER_CATEGORY + " tag '" + theBuffer.toString() + "' - Complete tag value was: " + theCompleteHeaderValue); + } + + } + + public static MethodOutcome process2xxResponse(FhirContext theContext, String theResourceName, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map> theHeaders) { + List locationHeaders = new ArrayList(); + List lh = theHeaders.get(Constants.HEADER_LOCATION_LC); + if (lh != null) { + locationHeaders.addAll(lh); + } + List clh = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); + if (clh != null) { + locationHeaders.addAll(clh); + } + + MethodOutcome retVal = new MethodOutcome(); + if (locationHeaders != null && locationHeaders.size() > 0) { + String locationHeader = locationHeaders.get(0); + BaseOutcomeReturningMethodBinding.parseContentLocation(retVal, theResourceName, locationHeader); + } + if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) { + EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType); + if (ct != null) { + PushbackReader reader = new PushbackReader(theResponseReader); + + try { + int firstByte = reader.read(); + if (firstByte == -1) { + BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read"); + reader = null; + } else { + reader.unread(firstByte); + } + } catch (IOException e) { + BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read", e); + reader = null; + } + + if (reader != null) { + IParser parser = ct.newParser(theContext); + IResource outcome = parser.parseResource(reader); + if (outcome instanceof BaseOperationOutcome) { + retVal.setOperationOutcome((BaseOperationOutcome) outcome); + } + } + + } else { + BaseOutcomeReturningMethodBinding.ourLog.debug("Ignoring response content of type: {}", theResponseMimeType); + } + } + return retVal; + } + + public static IQueryParameterOr singleton(final IQueryParameterType theParam) { + return new IQueryParameterOr() { + + @Override + public List getValuesAsQueryTokens() { + return Collections.singletonList(theParam); + } + + @Override + public void setValuesAsQueryTokens(QualifiedParamList theParameters) { + if (theParameters.isEmpty()) { + return; + } + if (theParameters.size() > 1) { + throw new IllegalArgumentException("Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); + } + theParam.setValueAsQueryToken(theParameters.getQualifier(), theParameters.get(0)); + } + }; + } + } 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 477d9a00b0a..d285cb98eb2 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 @@ -111,6 +111,8 @@ public class Constants { public static final int STATUS_HTTP_501_NOT_IMPLEMENTED = 501; public static final String URL_TOKEN_HISTORY = "_history"; public static final String URL_TOKEN_METADATA = "metadata"; + public static final String HEADER_IF_NONE_EXIST = "If-None-Exist"; + public static final String HEADER_IF_NONE_EXIST_LC = HEADER_IF_NONE_EXIST.toLowerCase(); static { Map valToEncoding = new HashMap(); diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index 5877c7e53c1..5df2930f93a 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -24,7 +24,16 @@ ca.uhn.fhir.validation.FhirValidator.noPhlocError=Phloc-schematron library not f # JPA Messages -ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request. -ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionOperationWithMultipleMatchFailure=Failed to {0} resource with match URL "{1}" because this search matched {2} resources -ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionOperationFailedNoId=Failed to {0} resource in transaction because no ID was provided -ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1} +ca.uhn.fhir.jpa.dao.BaseFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request. +ca.uhn.fhir.jpa.dao.BaseFhirDao.transactionOperationWithMultipleMatchFailure=Failed to {0} resource with match URL "{1}" because this search matched {2} resources +ca.uhn.fhir.jpa.dao.BaseFhirDao.transactionOperationFailedNoId=Failed to {0} resource in transaction because no ID was provided +ca.uhn.fhir.jpa.dao.BaseFhirDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1} + +ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionContainsMultipleWithDuplicateId=Transaction bundle contains multiple resources with ID: {0} +ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionEntryHasInvalidVerb=Transaction bundle entry has missing or invalid HTTP Verb specified in Bundle.entry.transaction.method. Found value: "{0}" +ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionMissingUrl=Unable to perform {0}, no URL provided. +ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionInvalidUrl=Unable to perform {0}, URL provided is invalid: {1} + +ca.uhn.fhir.jpa.dao.FhirResourceDao.duplicateCreateForcedId=Can not create entity with ID[{0}], a resource with this ID already exists +ca.uhn.fhir.jpa.dao.FhirResourceDao.failedToCreateWithClientAssignedNumericId=Can not create entity with ID[{0}], this server does not allow clients to assign numeric IDs +ca.uhn.fhir.jpa.dao.FhirResourceDao.unableToDeleteNotFound=Unable to find resource matching URL "{0}". Deletion failed. diff --git a/hapi-fhir-jpaserver-base/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-jpaserver-base/.settings/org.eclipse.jdt.core.prefs index d535869bf65..42246e57dab 100644 --- a/hapi-fhir-jpaserver-base/.settings/org.eclipse.jdt.core.prefs +++ b/hapi-fhir-jpaserver-base/.settings/org.eclipse.jdt.core.prefs @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nul org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.6 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -97,4 +97,4 @@ org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java index a8f1eb5d37b..16b747575b4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java @@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.dao; import static org.apache.commons.lang3.StringUtils.*; import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; import java.text.Normalizer; import java.util.ArrayList; import java.util.Collection; @@ -48,6 +50,8 @@ import javax.persistence.criteria.Root; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; import org.hl7.fhir.instance.model.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; @@ -77,6 +81,7 @@ import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTag; import ca.uhn.fhir.jpa.entity.TagDefinition; import ca.uhn.fhir.jpa.util.StopWatch; +import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; @@ -87,6 +92,8 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.method.MethodUtil; +import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -95,15 +102,16 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.FhirTerser; import com.google.common.base.Function; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; public abstract class BaseFhirDao implements IDao { - public static final String UCUM_NS = "http://unitsofmeasure.org"; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseFhirDao.class); + private static final Map ourRetrievalContexts = new HashMap(); + public static final String UCUM_NS = "http://unitsofmeasure.org"; @Autowired(required = true) private DaoConfig myConfig; @@ -114,8 +122,6 @@ public abstract class BaseFhirDao implements IDao { private EntityManager myEntityManager; private List myListeners = new ArrayList(); - private ISearchParamExtractor mySearchParamExtractor; - @Autowired private PlatformTransactionManager myPlatformTransactionManager; @@ -124,6 +130,8 @@ public abstract class BaseFhirDao implements IDao { private Map, IFhirResourceDao> myResourceTypeToDao; + private ISearchParamExtractor mySearchParamExtractor; + protected void createForcedIdIfNeeded(ResourceTable entity, IdDt id) { if (id.isEmpty() == false && id.hasIdPart()) { if (isValidPid(id)) { @@ -196,7 +204,7 @@ public abstract class BaseFhirDao implements IDao { b.append(nextValue.getReference().getResourceType()); b.append("] - Valid resource types for this server: "); b.append(myResourceTypeToDao.keySet().toString()); - + throw new InvalidRequestException(b.toString()); } Long valueOf; @@ -508,19 +516,6 @@ public abstract class BaseFhirDao implements IDao { return retVal; } - protected static String normalizeString(String theString) { - char[] out = new char[theString.length()]; - theString = Normalizer.normalize(theString, Normalizer.Form.NFD); - int j = 0; - for (int i = 0, n = theString.length(); i < n; ++i) { - char c = theString.charAt(i); - if (c <= '\u007F') { - out[j++] = c; - } - } - return new String(out).toUpperCase(); - } - protected void notifyWriteCompleted() { for (IDaoListener next : myListeners) { next.writeCompleted(); @@ -579,6 +574,58 @@ public abstract class BaseFhirDao implements IDao { } + protected Set processMatchUrl(String theMatchUrl, Class theResourceType) { + RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType); + + SearchParameterMap paramMap = translateMatchUrl(theMatchUrl, resourceDef); + + IFhirResourceDao dao = getDao(theResourceType); + Set ids = dao.searchForIdsWithAndOr(paramMap); + + return ids; + } + + protected SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) { + SearchParameterMap paramMap = new SearchParameterMap(); + List parameters; + try { + parameters = URLEncodedUtils.parse(new URI(theMatchUrl), "UTF-8"); + } catch (URISyntaxException e) { + throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Error was: " + e.toString()); + } + + ArrayListMultimap nameToParamLists = ArrayListMultimap.create(); + for (NameValuePair next : parameters) { + String paramName = next.getName(); + String qualifier = null; + for (int i = 0; i < paramMap.size(); i++) { + switch (paramName.charAt(i)) { + case '.': + case ':': + qualifier = paramName.substring(i); + paramName = paramName.substring(0, i); + i = Integer.MAX_VALUE; + break; + } + } + + QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue()); + nameToParamLists.put(paramName, paramList); + } + + for (String nextParamName : nameToParamLists.keySet()) { + RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName); + if (paramDef == null) { + throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); + } + + List paramList = nameToParamLists.get(nextParamName); + IQueryParameterAnd param = MethodUtil.parseQueryParams(paramDef, nextParamName, paramList); + paramMap.add(nextParamName, param); + } + return paramMap; + } + @Override public void registerDaoListener(IDaoListener theListener) { Validate.notNull(theListener, "theListener"); @@ -832,7 +879,20 @@ public abstract class BaseFhirDao implements IDao { } } + protected String translatePidIdToForcedId(Long theId) { + ForcedId forcedId = myEntityManager.find(ForcedId.class, theId); + if (forcedId != null) { + return forcedId.getForcedId(); + } else { + return theId.toString(); + } + } + protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, Date theDeletedTimestampOrNull) { + return updateEntity(theResource, entity, theUpdateHistory, theDeletedTimestampOrNull, true,true); + } + + protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion) { if (entity.getPublished() == null) { entity.setPublished(new Date()); } @@ -849,8 +909,10 @@ public abstract class BaseFhirDao implements IDao { myEntityManager.persist(historyEntry); } - entity.setVersion(entity.getVersion() + 1); - + if (theUpdateVersion) { + entity.setVersion(entity.getVersion() + 1); + } + Collection paramsString = new ArrayList(entity.getParamsString()); Collection paramsToken = new ArrayList(entity.getParamsToken()); Collection paramsNumber = new ArrayList(entity.getParamsNumber()); @@ -858,12 +920,13 @@ public abstract class BaseFhirDao implements IDao { Collection paramsDate = new ArrayList(entity.getParamsDate()); Collection resourceLinks = new ArrayList(entity.getResourceLinks()); - final List stringParams; - final List tokenParams; - final List numberParams; - final List quantityParams; - final List dateParams; - final List links; + List stringParams = null; + List tokenParams = null; + List numberParams = null; + List quantityParams = null; + List dateParams = null; + List links = null; + if (theDeletedTimestampOrNull != null) { stringParams = Collections.emptyList(); @@ -875,7 +938,7 @@ public abstract class BaseFhirDao implements IDao { entity.setDeleted(theDeletedTimestampOrNull); entity.setUpdated(theDeletedTimestampOrNull); - } else { + } else if (thePerformIndexing) { stringParams = extractSearchParamStrings(entity, theResource); numberParams = extractSearchParamNumber(entity, theResource); @@ -893,7 +956,6 @@ public abstract class BaseFhirDao implements IDao { links = extractResourceLinks(entity, theResource); populateResourceIntoEntity(theResource, entity); - entity.setUpdated(new Date()); entity.setLanguage(theResource.getLanguage().getValue()); entity.setParamsString(stringParams); @@ -909,6 +971,12 @@ public abstract class BaseFhirDao implements IDao { entity.setResourceLinks(links); entity.setHasLinks(links.isEmpty() == false); + } else { + + populateResourceIntoEntity(theResource, entity); + entity.setUpdated(new Date()); + entity.setLanguage(theResource.getLanguage().getValue()); + } if (entity.getId() == null) { @@ -922,59 +990,63 @@ public abstract class BaseFhirDao implements IDao { entity = myEntityManager.merge(entity); } - if (entity.isParamsStringPopulated()) { - for (ResourceIndexedSearchParamString next : paramsString) { - myEntityManager.remove(next); - } - } - for (ResourceIndexedSearchParamString next : stringParams) { - myEntityManager.persist(next); - } + if (thePerformIndexing) { - if (entity.isParamsTokenPopulated()) { - for (ResourceIndexedSearchParamToken next : paramsToken) { - myEntityManager.remove(next); + if (entity.isParamsStringPopulated()) { + for (ResourceIndexedSearchParamString next : paramsString) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamString next : stringParams) { + myEntityManager.persist(next); } - } - for (ResourceIndexedSearchParamToken next : tokenParams) { - myEntityManager.persist(next); - } - if (entity.isParamsNumberPopulated()) { - for (ResourceIndexedSearchParamNumber next : paramsNumber) { - myEntityManager.remove(next); + if (entity.isParamsTokenPopulated()) { + for (ResourceIndexedSearchParamToken next : paramsToken) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamToken next : tokenParams) { + myEntityManager.persist(next); } - } - for (ResourceIndexedSearchParamNumber next : numberParams) { - myEntityManager.persist(next); - } - if (entity.isParamsQuantityPopulated()) { - for (ResourceIndexedSearchParamQuantity next : paramsQuantity) { - myEntityManager.remove(next); + if (entity.isParamsNumberPopulated()) { + for (ResourceIndexedSearchParamNumber next : paramsNumber) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamNumber next : numberParams) { + myEntityManager.persist(next); } - } - for (ResourceIndexedSearchParamQuantity next : quantityParams) { - myEntityManager.persist(next); - } - if (entity.isParamsDatePopulated()) { - for (ResourceIndexedSearchParamDate next : paramsDate) { - myEntityManager.remove(next); + if (entity.isParamsQuantityPopulated()) { + for (ResourceIndexedSearchParamQuantity next : paramsQuantity) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamQuantity next : quantityParams) { + myEntityManager.persist(next); } - } - for (ResourceIndexedSearchParamDate next : dateParams) { - myEntityManager.persist(next); - } - if (entity.isHasLinks()) { - for (ResourceLink next : resourceLinks) { - myEntityManager.remove(next); + if (entity.isParamsDatePopulated()) { + for (ResourceIndexedSearchParamDate next : paramsDate) { + myEntityManager.remove(next); + } } - } - for (ResourceLink next : links) { - myEntityManager.persist(next); - } + for (ResourceIndexedSearchParamDate next : dateParams) { + myEntityManager.persist(next); + } + + if (entity.isHasLinks()) { + for (ResourceLink next : resourceLinks) { + myEntityManager.remove(next); + } + } + for (ResourceLink next : links) { + myEntityManager.persist(next); + } + + } // if thePerformIndexing myEntityManager.flush(); @@ -985,4 +1057,17 @@ public abstract class BaseFhirDao implements IDao { return entity; } + protected static String normalizeString(String theString) { + char[] out = new char[theString.length()]; + theString = Normalizer.normalize(theString, Normalizer.Form.NFD); + int j = 0; + for (int i = 0, n = theString.length(); i < n; ++i) { + char c = theString.charAt(i); + if (c <= '\u007F') { + out[j++] = c; + } + } + return new String(out).toUpperCase(); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirSystemDao.java index 2e8c353673c..8a82b7d6c74 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirSystemDao.java @@ -20,11 +20,8 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import java.net.URI; -import java.net.URISyntaxException; import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; @@ -36,27 +33,14 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; -import org.hl7.fhir.instance.model.IBaseResource; - -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.util.StopWatch; -import ca.uhn.fhir.model.api.IQueryParameterAnd; -import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.rest.method.MethodUtil; -import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import com.google.common.collect.ArrayListMultimap; - public abstract class BaseFhirSystemDao extends BaseFhirDao implements IFhirSystemDao { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseFhirSystemDao.class); @@ -82,53 +66,6 @@ public abstract class BaseFhirSystemDao extends BaseFhirDao implements IFhirS return myEntityManager.find(ResourceTable.class, candidateMatches.iterator().next()); } - protected Set processMatchUrl(String theMatchUrl, Class theResourceType) { - RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType); - - SearchParameterMap paramMap = new SearchParameterMap(); - List parameters; - try { - parameters = URLEncodedUtils.parse(new URI(theMatchUrl), "UTF-8"); - } catch (URISyntaxException e) { - throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Error was: " + e.toString()); - } - - ArrayListMultimap nameToParamLists = ArrayListMultimap.create(); - for (NameValuePair next : parameters) { - String paramName = next.getName(); - String qualifier = null; - for (int i = 0; i < paramMap.size(); i++) { - switch (paramName.charAt(i)) { - case '.': - case ':': - qualifier = paramName.substring(i); - paramName = paramName.substring(0, i); - i = Integer.MAX_VALUE; - break; - } - } - - QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue()); - nameToParamLists.put(paramName, paramList); - } - - for (String nextParamName : nameToParamLists.keySet()) { - RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName); - if (paramDef == null) { - throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); - } - - List paramList = nameToParamLists.get(nextParamName); - IQueryParameterAnd param = MethodUtil.parseQueryParams(paramDef, nextParamName, paramList); - paramMap.add(nextParamName, param); - } - - IFhirResourceDao dao = getDao(theResourceType); - Set ids = dao.searchForIdsWithAndOr(paramMap); - - return ids; - } - @Override public IBundleProvider history(Date theSince) { StopWatch w = new StopWatch(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java new file mode 100644 index 00000000000..89cef618eab --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.jpa.dao; + +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.api.MethodOutcome; + +public class DaoMethodOutcome extends MethodOutcome { + + private ResourceTable myEntity; + private IResource myResource; + + public ResourceTable getEntity() { + return myEntity; + } + + public IResource getResource() { + return myResource; + } + + @Override + public DaoMethodOutcome setCreated(Boolean theCreated) { + super.setCreated(theCreated); + return this; + } + + public DaoMethodOutcome setEntity(ResourceTable theEntity) { + myEntity = theEntity; + return this; + } + + public DaoMethodOutcome setResource(IResource theResource) { + myResource = theResource; + return this; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java index ffa9690f863..a7f0f8f9e3a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java @@ -20,8 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.*; import java.math.BigDecimal; import java.util.ArrayList; @@ -97,7 +96,6 @@ import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; -import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; @@ -111,6 +109,7 @@ import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 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.UnprocessableEntityException; @@ -132,658 +131,6 @@ public class FhirResourceDao extends BaseFhirDao implements private Class myResourceType; private String mySecondaryPrimaryKeyParamName; - @Override - public void addTag(IdDt theId, String theScheme, String theTerm, String theLabel) { - StopWatch w = new StopWatch(); - BaseHasResource entity = readEntity(theId); - if (entity == null) { - throw new ResourceNotFoundException(theId); - } - - for (BaseTag next : new ArrayList(entity.getTags())) { - if (ObjectUtil.equals(next.getTag().getScheme(), theScheme) && ObjectUtil.equals(next.getTag().getTerm(), theTerm)) { - return; - } - } - - entity.setHasTags(true); - - TagDefinition def = getTag(theScheme, theTerm, theLabel); - BaseTag newEntity = entity.addTag(def); - - myEntityManager.persist(newEntity); - myEntityManager.merge(entity); - notifyWriteCompleted(); - ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() }); - } - - @Override - public MethodOutcome create(final T theResource) { - StopWatch w = new StopWatch(); - ResourceTable entity = new ResourceTable(); - entity.setResourceType(toResourceName(theResource)); - - if (theResource.getId().isEmpty() == false) { - if (isValidPid(theResource.getId())) { - throw new UnprocessableEntityException( - "This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID"); - } - createForcedIdIfNeeded(entity, theResource.getId()); - - if (entity.getForcedId() != null) { - try { - translateForcedIdToPid(theResource.getId()); - throw new UnprocessableEntityException("Can not create entity with ID[" + theResource.getId().getValue() + "], constraint violation occurred"); - } catch (ResourceNotFoundException e) { - // good, this ID doesn't exist so we can create it - } - } - - } - - updateEntity(theResource, entity, false, null); - - MethodOutcome outcome = toMethodOutcome(entity); - notifyWriteCompleted(); - ourLog.info("Processed create on {} in {}ms", myResourceName, w.getMillisAndRestart()); - return outcome; - } - - @Override - public MethodOutcome delete(IdDt theId) { - StopWatch w = new StopWatch(); - final ResourceTable entity = readEntityLatestVersion(theId); - if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { - throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version"); - } - - ResourceTable savedEntity = updateEntity(null, entity, true, new Date()); - - notifyWriteCompleted(); - - ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); - return toMethodOutcome(savedEntity); - } - - // private Set addPredicateComposite(String theParamName, Set thePids, List theList) { - // } - - @Override - public TagList getAllResourceTags() { - StopWatch w = new StopWatch(); - TagList tags = super.getTags(myResourceType, null); - ourLog.info("Processed getTags on {} in {}ms", myResourceName, w.getMillisAndRestart()); - return tags; - } - - public Class getResourceType() { - return myResourceType; - } - - @Override - public TagList getTags(IdDt theResourceId) { - StopWatch w = new StopWatch(); - TagList retVal = super.getTags(myResourceType, theResourceId); - ourLog.info("Processed getTags on {} in {}ms", theResourceId, w.getMillisAndRestart()); - return retVal; - } - - @Override - public IBundleProvider history(Date theSince) { - StopWatch w = new StopWatch(); - IBundleProvider retVal = super.history(myResourceName, null, theSince); - ourLog.info("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart()); - return retVal; - } - - @Override - public IBundleProvider history(final IdDt theId, final Date theSince) { - final InstantDt end = createHistoryToTimestamp(); - final String resourceType = getContext().getResourceDefinition(myResourceType).getName(); - - T currentTmp; - try { - currentTmp = read(theId.toVersionless()); - if (ResourceMetadataKeyEnum.UPDATED.get(currentTmp).after(end.getValue())) { - currentTmp = null; - } - } catch (ResourceNotFoundException e) { - currentTmp = null; - } - - final T current = currentTmp; - - String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" - + (theSince != null ? " AND h.myUpdated >= :SINCE" : ""); - TypedQuery countQuery = myEntityManager.createQuery(querySring, Long.class); - countQuery.setParameter("PID", translateForcedIdToPid(theId)); - countQuery.setParameter("RESTYPE", resourceType); - countQuery.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); - if (theSince != null) { - countQuery.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); - } - int historyCount = countQuery.getSingleResult().intValue(); - - final int offset; - final int count; - if (current != null) { - count = historyCount + 1; - offset = 1; - } else { - offset = 0; - count = historyCount; - } - - if (count == 0) { - throw new ResourceNotFoundException(theId); - } - - return new IBundleProvider() { - - @Override - public InstantDt getPublished() { - return end; - } - - @Override - public List getResources(int theFromIndex, int theToIndex) { - ArrayList retVal = new ArrayList(); - if (theFromIndex == 0 && current != null) { - retVal.add(current); - } - - TypedQuery q = myEntityManager.createQuery( - "SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " - + (theSince != null ? " AND h.myUpdated >= :SINCE" : "") + " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class); - q.setParameter("PID", translateForcedIdToPid(theId)); - q.setParameter("RESTYPE", resourceType); - q.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); - if (theSince != null) { - q.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); - } - - q.setFirstResult(Math.max(0, theFromIndex - offset)); - q.setMaxResults(theToIndex - theFromIndex); - - List results = q.getResultList(); - for (ResourceHistoryTable next : results) { - if (retVal.size() == (theToIndex - theFromIndex)) { - break; - } - retVal.add(toResource(myResourceType, next)); - } - - return retVal; - } - - @Override - public int size() { - return count; - } - }; - - } - - @Override - public IBundleProvider history(Long theId, Date theSince) { - StopWatch w = new StopWatch(); - IBundleProvider retVal = super.history(myResourceName, theId, theSince); - ourLog.info("Processed history on {} in {}ms", theId, w.getMillisAndRestart()); - return retVal; - } - - @PostConstruct - public void postConstruct() { - RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType); - myResourceName = def.getName(); - - if (mySecondaryPrimaryKeyParamName != null) { - RuntimeSearchParam sp = def.getSearchParam(mySecondaryPrimaryKeyParamName); - if (sp == null) { - throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); - } - if (sp.getParamType() != SearchParamTypeEnum.TOKEN) { - throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName - + "] is not a token type, only token is supported"); - } - } - - } - - @Override - public T read(IdDt theId) { - validateResourceTypeAndThrowIllegalArgumentException(theId); - - StopWatch w = new StopWatch(); - BaseHasResource entity = readEntity(theId); - validateResourceType(entity); - - T retVal = toResource(myResourceType, entity); - - InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(retVal); - if (deleted != null && !deleted.isEmpty()) { - throw new ResourceGoneException("Resource was deleted at " + deleted.getValueAsString()); - } - - ourLog.info("Processed read on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); - return retVal; - } - - @Override - public BaseHasResource readEntity(IdDt theId) { - boolean checkForForcedId = true; - - BaseHasResource entity = readEntity(theId, checkForForcedId); - - return entity; - } - - @Override - public BaseHasResource readEntity(IdDt theId, boolean theCheckForForcedId) { - validateResourceTypeAndThrowIllegalArgumentException(theId); - - Long pid = translateForcedIdToPid(theId); - BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid); - if (theId.hasVersionIdPart()) { - if (entity.getVersion() != theId.getVersionIdPartAsLong()) { - entity = null; - } - } - - if (entity == null) { - if (theId.hasVersionIdPart()) { - TypedQuery q = myEntityManager.createQuery( - "SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class); - q.setParameter("RID", pid); - q.setParameter("RTYP", myResourceName); - q.setParameter("RVER", theId.getVersionIdPartAsLong()); - entity = q.getSingleResult(); - } - if (entity == null) { - throw new ResourceNotFoundException(theId); - } - } - - - validateResourceType(entity); - - if (theCheckForForcedId) { - validateGivenIdIsAppropriateToRetrieveResource(theId, entity); - } - return entity; - } - - @Override - public void removeTag(IdDt theId, String theScheme, String theTerm) { - StopWatch w = new StopWatch(); - BaseHasResource entity = readEntity(theId); - if (entity == null) { - throw new ResourceNotFoundException(theId); - } - - for (BaseTag next : new ArrayList(entity.getTags())) { - if (next.getTag().getScheme().equals(theScheme) && next.getTag().getTerm().equals(theTerm)) { - myEntityManager.remove(next); - entity.getTags().remove(next); - } - } - - if (entity.getTags().isEmpty()) { - entity.setHasTags(false); - } - - myEntityManager.merge(entity); - - ourLog.info("Processed remove tag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId.getValue(), w.getMillisAndRestart() }); - } - - @Override - public IBundleProvider search(Map theParams) { - SearchParameterMap map = new SearchParameterMap(); - for (Entry nextEntry : theParams.entrySet()) { - map.add(nextEntry.getKey(), (nextEntry.getValue())); - } - return search(map); - } - - @Override - public IBundleProvider search(final SearchParameterMap theParams) { - StopWatch w = new StopWatch(); - final InstantDt now = InstantDt.withCurrentTime(); - - Set loadPids; - if (theParams.isEmpty()) { - loadPids = new HashSet(); - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createTupleQuery(); - Root from = cq.from(ResourceTable.class); - cq.multiselect(from.get("myId").as(Long.class)); - cq.where(builder.equal(from.get("myResourceType"), myResourceName)); - - TypedQuery query = myEntityManager.createQuery(cq); - for (Tuple next : query.getResultList()) { - loadPids.add(next.get(0, Long.class)); - } - } else { - loadPids = searchForIdsWithAndOr(theParams); - if (loadPids.isEmpty()) { - return new SimpleBundleProvider(); - } - } - - final List pids = new ArrayList(loadPids); - - // Handle sorting if any was provided - if (theParams.getSort() != null && isNotBlank(theParams.getSort().getParamName())) { - List orders = new ArrayList(); - List predicates = new ArrayList(); - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createTupleQuery(); - Root from = cq.from(ResourceTable.class); - predicates.add(from.get("myId").in(pids)); - createSort(builder, from, theParams.getSort(), orders, predicates); - if (orders.size() > 0) { - loadPids = new LinkedHashSet(); - cq.multiselect(from.get("myId").as(Long.class)); - cq.where(predicates.toArray(new Predicate[0])); - cq.orderBy(orders); - - TypedQuery query = myEntityManager.createQuery(cq); - - for (Tuple next : query.getResultList()) { - loadPids.add(next.get(0, Long.class)); - } - - ourLog.info("Sort PID order is now: {}", loadPids); - - pids.clear(); - pids.addAll(loadPids); - } - } - - IBundleProvider retVal = new IBundleProvider() { - - @Override - public InstantDt getPublished() { - return now; - } - - @Override - public List getResources(final int theFromIndex, final int theToIndex) { - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - return template.execute(new TransactionCallback>() { - @Override - public List doInTransaction(TransactionStatus theStatus) { - List pidsSubList = pids.subList(theFromIndex, theToIndex); - - // Execute the query and make sure we return distinct results - List retVal = new ArrayList(); - loadResourcesByPid(pidsSubList, retVal, BundleEntrySearchModeEnum.MATCH); - - // Load _include resources - if (theParams.getIncludes() != null && theParams.getIncludes().isEmpty() == false) { - Set previouslyLoadedPids = new HashSet(); - - Set includePids = new HashSet(); - List resources = retVal; - do { - includePids.clear(); - - FhirTerser t = getContext().newTerser(); - for (Include next : theParams.getIncludes()) { - for (IResource nextResource : resources) { - RuntimeResourceDefinition def = getContext().getResourceDefinition(nextResource); - List values; - if ("*".equals(next.getValue())) { - values = new ArrayList(); - values.addAll(t.getAllPopulatedChildElementsOfType(nextResource, BaseResourceReferenceDt.class)); - } else if (next.getValue().startsWith(def.getName() + ".")) { - values = t.getValues(nextResource, next.getValue()); - } else { - continue; - } - - for (Object object : values) { - if (object == null) { - continue; - } - if (!(object instanceof BaseResourceReferenceDt)) { - throw new InvalidRequestException("Path '" + next.getValue() + "' produced non ResourceReferenceDt value: " + object.getClass()); - } - BaseResourceReferenceDt rr = (BaseResourceReferenceDt) object; - if (rr.getReference().isEmpty()) { - continue; - } - if (rr.getReference().isLocal()) { - continue; - } - - IdDt nextId = rr.getReference().toUnqualified(); - if (!previouslyLoadedPids.contains(nextId)) { - includePids.add(nextId); - previouslyLoadedPids.add(nextId); - } - } - } - } - - if (!includePids.isEmpty()) { - ourLog.info("Loading {} included resources", includePids.size()); - resources = loadResourcesById(includePids); - for (IResource next : resources) { - ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(next, BundleEntrySearchModeEnum.INCLUDE); - } - retVal.addAll(resources); - } - } while (includePids.size() > 0 && previouslyLoadedPids.size() < getConfig().getIncludeLimit()); - - if (previouslyLoadedPids.size() >= getConfig().getIncludeLimit()) { - OperationOutcome oo = new OperationOutcome(); - oo.addIssue().setSeverity(IssueSeverityEnum.WARNING) - .setDetails("Not all _include resources were actually included as the request surpassed the limit of " + getConfig().getIncludeLimit() + " resources"); - retVal.add(0, oo); - } - } - - return retVal; - } - }); - } - - @Override - public int size() { - return pids.size(); - } - }; - - ourLog.info("Processed search for {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() }); - - return retVal; - } - - @Override - public IBundleProvider search(String theParameterName, IQueryParameterType theValue) { - return search(Collections.singletonMap(theParameterName, theValue)); - } - - @Override - public Set searchForIds(Map theParams) { - SearchParameterMap map = new SearchParameterMap(); - for (Entry nextEntry : theParams.entrySet()) { - map.add(nextEntry.getKey(), (nextEntry.getValue())); - } - return searchForIdsWithAndOr(map); - } - - @Override - public Set searchForIds(String theParameterName, IQueryParameterType theValue) { - return searchForIds(Collections.singletonMap(theParameterName, theValue)); - } - - @Override - public Set searchForIdsWithAndOr(SearchParameterMap theParams) { - SearchParameterMap params = theParams; - if (params == null) { - params = new SearchParameterMap(); - } - - RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceType); - - Set pids = new HashSet(); - - for (Entry>> nextParamEntry : params.entrySet()) { - String nextParamName = nextParamEntry.getKey(); - if (nextParamName.equals("_id")) { - - if (nextParamEntry.getValue().isEmpty()) { - continue; - } else if (nextParamEntry.getValue().size() > 1) { - throw new InvalidRequestException("AND queries not supported for _id (Multiple instances of this param found)"); - } else { - Set joinPids = new HashSet(); - List nextValue = nextParamEntry.getValue().get(0); - if (nextValue == null || nextValue.size() == 0) { - continue; - } else { - for (IQueryParameterType next : nextValue) { - String value = next.getValueAsQueryToken(); - IdDt valueId = new IdDt(value); - try { - long valueLong = translateForcedIdToPid(valueId); - joinPids.add(valueLong); - } catch (ResourceNotFoundException e) { - // This isn't an error, just means no result found - } - } - if (joinPids.isEmpty()) { - continue; - } - } - - pids = addPredicateId(pids, joinPids); - if (pids.isEmpty()) { - return new HashSet(); - } - - if (pids.isEmpty()) { - pids.addAll(joinPids); - } else { - pids.retainAll(joinPids); - } - } - - } else if (nextParamName.equals("_language")) { - - pids = addPredicateLanguage(pids, nextParamEntry.getValue()); - - } else { - - RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); - if (nextParamDef != null) { - switch (nextParamDef.getParamType()) { - case DATE: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateDate(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case QUANTITY: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateQuantity(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case REFERENCE: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateReference(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case STRING: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateString(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case TOKEN: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateToken(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case NUMBER: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateNumber(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case COMPOSITE: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateComposite(nextParamDef, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - } - } - } - } - - return pids; - } - - @SuppressWarnings("unchecked") - @Required - public void setResourceType(Class theTableType) { - myResourceType = (Class) theTableType; - } - - /** - * If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to share the same value. - */ - public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) { - mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName; - } - - @Override - public MethodOutcome update(final T theResource, final IdDt theId) { - StopWatch w = new StopWatch(); - - // TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - // ResourceTable savedEntity = template.execute(new TransactionCallback() { - // @Override - // public ResourceTable doInTransaction(TransactionStatus theStatus) { - // final ResourceTable entity = readEntity(theId); - // return updateEntity(theResource, entity,true); - // } - // }); - - final ResourceTable entity = readEntityLatestVersion(theId); - if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { - throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version"); - } - - ResourceTable savedEntity = updateEntity(theResource, entity, true, null); - - notifyWriteCompleted(); - ourLog.info("Processed update on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); - return toMethodOutcome(savedEntity); - } - private Set addPredicateComposite(RuntimeSearchParam theParamDef, Set thePids, List theNextAnd) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createQuery(Long.class); @@ -889,6 +236,10 @@ public class FhirResourceDao extends BaseFhirDao implements } } + // private Set addPredicateComposite(String theParamName, Set thePids, List theList) { + // } + private Set addPredicateId(Set theExistingPids, Set thePids) { if (thePids == null || thePids.isEmpty()) { return Collections.emptySet(); @@ -1295,6 +646,91 @@ public class FhirResourceDao extends BaseFhirDao implements return new HashSet(q.getResultList()); } + @Override + public void addTag(IdDt theId, String theScheme, String theTerm, String theLabel) { + StopWatch w = new StopWatch(); + BaseHasResource entity = readEntity(theId); + if (entity == null) { + throw new ResourceNotFoundException(theId); + } + + for (BaseTag next : new ArrayList(entity.getTags())) { + if (ObjectUtil.equals(next.getTag().getScheme(), theScheme) && ObjectUtil.equals(next.getTag().getTerm(), theTerm)) { + return; + } + } + + entity.setHasTags(true); + + TagDefinition def = getTag(theScheme, theTerm, theLabel); + BaseTag newEntity = entity.addTag(def); + + myEntityManager.persist(newEntity); + myEntityManager.merge(entity); + notifyWriteCompleted(); + ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() }); + } + + @Override + public DaoMethodOutcome create(final T theResource) { + return create(theResource, null, true); + } + + @Override + public DaoMethodOutcome create(final T theResource, String theIfNoneExist) { + return create(theResource, theIfNoneExist, true); + } + + @Override + public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing) { + StopWatch w = new StopWatch(); + ResourceTable entity = new ResourceTable(); + entity.setResourceType(toResourceName(theResource)); + + if (isNotBlank(theResource.getId().getIdPart())) { + if (theResource.getId().isIdPartValidLong()) { + throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getId().getIdPart())); + } + } + + if (isNotBlank(theIfNoneExist)) { + Set match = processMatchUrl(theIfNoneExist, myResourceType); + if (match.size() > 1) { + String msg = getContext().getLocalizer().getMessage(BaseFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size()); + throw new PreconditionFailedException(msg); + } else if (match.size() == 1) { + Long pid = match.iterator().next(); + entity = myEntityManager.find(ResourceTable.class, pid); + return toMethodOutcome(entity, theResource).setCreated(false); + } + } + + if (theResource.getId().isEmpty() == false) { + if (isValidPid(theResource.getId())) { + throw new UnprocessableEntityException("This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID"); + } + createForcedIdIfNeeded(entity, theResource.getId()); + + if (entity.getForcedId() != null) { + try { + translateForcedIdToPid(theResource.getId()); + throw new UnprocessableEntityException(getContext().getLocalizer().getMessage(FhirResourceDao.class, "duplicateCreateForcedId", theResource.getId().getIdPart())); + } catch (ResourceNotFoundException e) { + // good, this ID doesn't exist so we can create it + } + } + + } + + updateEntity(theResource, entity, false, null, thePerformIndexing, true); + + DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true); + + notifyWriteCompleted(); + ourLog.info("Processed create on {} in {}ms", myResourceName, w.getMillisAndRestart()); + return outcome; + } + private Predicate createCompositeParamPart(CriteriaBuilder builder, Root from, RuntimeSearchParam left, IQueryParameterType leftValue) { Predicate retVal = null; switch (left.getParamType()) { @@ -1342,8 +778,7 @@ public class FhirResourceDao extends BaseFhirDao implements return p; } - private Predicate createPredicateString(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, - From theFrom) { + private Predicate createPredicateString(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From theFrom) { String rawSearchTerm; if (theParameter instanceof TokenParam) { TokenParam id = (TokenParam) theParameter; @@ -1362,8 +797,7 @@ public class FhirResourceDao extends BaseFhirDao implements } if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { - throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" - + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); + throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); } String likeExpression = normalizeString(rawSearchTerm); @@ -1377,8 +811,7 @@ public class FhirResourceDao extends BaseFhirDao implements return singleCode; } - private Predicate createPredicateToken(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, - From theFrom) { + private Predicate createPredicateToken(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From theFrom) { String code; String system; if (theParameter instanceof TokenParam) { @@ -1398,12 +831,10 @@ public class FhirResourceDao extends BaseFhirDao implements } if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { - throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH - + "): " + system); + throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system); } if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { - throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH - + "): " + code); + throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code); } ArrayList singleCodePredicates = (new ArrayList()); @@ -1452,6 +883,171 @@ public class FhirResourceDao extends BaseFhirDao implements createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates); } + @Override + public DaoMethodOutcome delete(IdDt theId) { + StopWatch w = new StopWatch(); + final ResourceTable entity = readEntityLatestVersion(theId); + if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { + throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version"); + } + + ResourceTable savedEntity = updateEntity(null, entity, true, new Date()); + + notifyWriteCompleted(); + + ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); + return toMethodOutcome(savedEntity, null); + } + + @Override + public DaoMethodOutcome deleteByUrl(String theUrl) { + StopWatch w = new StopWatch(); + + Set resource = processMatchUrl(theUrl, myResourceType); + if (resource.isEmpty()) { + throw new ResourceNotFoundException(getContext().getLocalizer().getMessage(FhirResourceDao.class, "unableToDeleteNotFound", theUrl)); + } else if (resource.size() > 1) { + throw new ResourceNotFoundException(getContext().getLocalizer().getMessage(BaseFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size())); + } + + Long pid = resource.iterator().next(); + + ResourceTable entity = myEntityManager.find(ResourceTable.class, pid); + + ResourceTable savedEntity = updateEntity(null, entity, true, new Date()); + notifyWriteCompleted(); + + ourLog.info("Processed delete on {} in {}ms", theUrl, w.getMillisAndRestart()); + return toMethodOutcome(savedEntity, null); + } + + @Override + public TagList getAllResourceTags() { + StopWatch w = new StopWatch(); + TagList tags = super.getTags(myResourceType, null); + ourLog.info("Processed getTags on {} in {}ms", myResourceName, w.getMillisAndRestart()); + return tags; + } + + public Class getResourceType() { + return myResourceType; + } + + @Override + public TagList getTags(IdDt theResourceId) { + StopWatch w = new StopWatch(); + TagList retVal = super.getTags(myResourceType, theResourceId); + ourLog.info("Processed getTags on {} in {}ms", theResourceId, w.getMillisAndRestart()); + return retVal; + } + + @Override + public IBundleProvider history(Date theSince) { + StopWatch w = new StopWatch(); + IBundleProvider retVal = super.history(myResourceName, null, theSince); + ourLog.info("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart()); + return retVal; + } + + @Override + public IBundleProvider history(final IdDt theId, final Date theSince) { + final InstantDt end = createHistoryToTimestamp(); + final String resourceType = getContext().getResourceDefinition(myResourceType).getName(); + + T currentTmp; + try { + BaseHasResource entity = readEntity(theId.toVersionless(), false); + validateResourceType(entity); + currentTmp = toResource(myResourceType, entity); + if (ResourceMetadataKeyEnum.UPDATED.get(currentTmp).after(end.getValue())) { + currentTmp = null; + } + } catch (ResourceNotFoundException e) { + currentTmp = null; + } + + final T current = currentTmp; + + String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" + (theSince != null ? " AND h.myUpdated >= :SINCE" : ""); + TypedQuery countQuery = myEntityManager.createQuery(querySring, Long.class); + countQuery.setParameter("PID", translateForcedIdToPid(theId)); + countQuery.setParameter("RESTYPE", resourceType); + countQuery.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); + if (theSince != null) { + countQuery.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); + } + int historyCount = countQuery.getSingleResult().intValue(); + + final int offset; + final int count; + if (current != null) { + count = historyCount + 1; + offset = 1; + } else { + offset = 0; + count = historyCount; + } + + if (count == 0) { + throw new ResourceNotFoundException(theId); + } + + return new IBundleProvider() { + + @Override + public InstantDt getPublished() { + return end; + } + + @Override + public List getResources(int theFromIndex, int theToIndex) { + ArrayList retVal = new ArrayList(); + if (theFromIndex == 0 && current != null) { + retVal.add(current); + } + + TypedQuery q = myEntityManager.createQuery("SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " + (theSince != null ? " AND h.myUpdated >= :SINCE" : "") + " ORDER BY h.myUpdated ASC", + ResourceHistoryTable.class); + q.setParameter("PID", translateForcedIdToPid(theId)); + q.setParameter("RESTYPE", resourceType); + q.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); + if (theSince != null) { + q.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); + } + + int firstResult = Math.max(0, theFromIndex - offset); + q.setFirstResult(firstResult); + + int maxResults = (theToIndex - theFromIndex) + 1; + q.setMaxResults(maxResults); + + List results = q.getResultList(); + for (ResourceHistoryTable next : results) { + if (retVal.size() == maxResults) { + break; + } + retVal.add(toResource(myResourceType, next)); + } + + return retVal; + } + + @Override + public int size() { + return count; + } + }; + + } + + @Override + public IBundleProvider history(Long theId, Date theSince) { + StopWatch w = new StopWatch(); + IBundleProvider retVal = super.history(myResourceName, theId, theSince); + ourLog.info("Processed history on {} in {}ms", theId, w.getMillisAndRestart()); + return retVal; + } + private void loadResourcesByPid(Collection theIncludePids, List theResourceListToPopulate, BundleEntrySearchModeEnum theBundleEntryStatus) { if (theIncludePids.isEmpty()) { return; @@ -1479,13 +1075,91 @@ public class FhirResourceDao extends BaseFhirDao implements ourLog.warn("Got back unexpected resource PID {}", next.getId()); continue; } - + ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(resource, theBundleEntryStatus); theResourceListToPopulate.set(index, resource); } } + @PostConstruct + public void postConstruct() { + RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType); + myResourceName = def.getName(); + + if (mySecondaryPrimaryKeyParamName != null) { + RuntimeSearchParam sp = def.getSearchParam(mySecondaryPrimaryKeyParamName); + if (sp == null) { + throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); + } + if (sp.getParamType() != SearchParamTypeEnum.TOKEN) { + throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "] is not a token type, only token is supported"); + } + } + + } + + @Override + public T read(IdDt theId) { + validateResourceTypeAndThrowIllegalArgumentException(theId); + + StopWatch w = new StopWatch(); + BaseHasResource entity = readEntity(theId); + validateResourceType(entity); + + T retVal = toResource(myResourceType, entity); + + InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(retVal); + if (deleted != null && !deleted.isEmpty()) { + throw new ResourceGoneException("Resource was deleted at " + deleted.getValueAsString()); + } + + ourLog.info("Processed read on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); + return retVal; + } + + @Override + public BaseHasResource readEntity(IdDt theId) { + boolean checkForForcedId = true; + + BaseHasResource entity = readEntity(theId, checkForForcedId); + + return entity; + } + + @Override + public BaseHasResource readEntity(IdDt theId, boolean theCheckForForcedId) { + validateResourceTypeAndThrowIllegalArgumentException(theId); + + Long pid = translateForcedIdToPid(theId); + BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid); + if (theId.hasVersionIdPart()) { + if (entity.getVersion() != theId.getVersionIdPartAsLong()) { + entity = null; + } + } + + if (entity == null) { + if (theId.hasVersionIdPart()) { + TypedQuery q = myEntityManager.createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class); + q.setParameter("RID", pid); + q.setParameter("RTYP", myResourceName); + q.setParameter("RVER", theId.getVersionIdPartAsLong()); + entity = q.getSingleResult(); + } + if (entity == null) { + throw new ResourceNotFoundException(theId); + } + } + + validateResourceType(entity); + + if (theCheckForForcedId) { + validateGivenIdIsAppropriateToRetrieveResource(theId, entity); + } + return entity; + } + private ResourceTable readEntityLatestVersion(IdDt theId) { ResourceTable entity = myEntityManager.find(ResourceTable.class, translateForcedIdToPid(theId)); if (entity == null) { @@ -1495,9 +1169,359 @@ public class FhirResourceDao extends BaseFhirDao implements return entity; } - private MethodOutcome toMethodOutcome(final ResourceTable entity) { - MethodOutcome outcome = new MethodOutcome(); - outcome.setId(entity.getIdDt()); + @Override + public void removeTag(IdDt theId, String theScheme, String theTerm) { + StopWatch w = new StopWatch(); + BaseHasResource entity = readEntity(theId); + if (entity == null) { + throw new ResourceNotFoundException(theId); + } + + for (BaseTag next : new ArrayList(entity.getTags())) { + if (next.getTag().getScheme().equals(theScheme) && next.getTag().getTerm().equals(theTerm)) { + myEntityManager.remove(next); + entity.getTags().remove(next); + } + } + + if (entity.getTags().isEmpty()) { + entity.setHasTags(false); + } + + myEntityManager.merge(entity); + + ourLog.info("Processed remove tag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId.getValue(), w.getMillisAndRestart() }); + } + + @Override + public IBundleProvider search(Map theParams) { + SearchParameterMap map = new SearchParameterMap(); + for (Entry nextEntry : theParams.entrySet()) { + map.add(nextEntry.getKey(), (nextEntry.getValue())); + } + return search(map); + } + + @Override + public IBundleProvider search(final SearchParameterMap theParams) { + StopWatch w = new StopWatch(); + final InstantDt now = InstantDt.withCurrentTime(); + + Set loadPids; + if (theParams.isEmpty()) { + loadPids = new HashSet(); + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createTupleQuery(); + Root from = cq.from(ResourceTable.class); + cq.multiselect(from.get("myId").as(Long.class)); + cq.where(builder.equal(from.get("myResourceType"), myResourceName)); + + TypedQuery query = myEntityManager.createQuery(cq); + for (Tuple next : query.getResultList()) { + loadPids.add(next.get(0, Long.class)); + } + } else { + loadPids = searchForIdsWithAndOr(theParams); + if (loadPids.isEmpty()) { + return new SimpleBundleProvider(); + } + } + + final List pids = new ArrayList(loadPids); + + // Handle sorting if any was provided + if (theParams.getSort() != null && isNotBlank(theParams.getSort().getParamName())) { + List orders = new ArrayList(); + List predicates = new ArrayList(); + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createTupleQuery(); + Root from = cq.from(ResourceTable.class); + predicates.add(from.get("myId").in(pids)); + createSort(builder, from, theParams.getSort(), orders, predicates); + if (orders.size() > 0) { + loadPids = new LinkedHashSet(); + cq.multiselect(from.get("myId").as(Long.class)); + cq.where(predicates.toArray(new Predicate[0])); + cq.orderBy(orders); + + TypedQuery query = myEntityManager.createQuery(cq); + + for (Tuple next : query.getResultList()) { + loadPids.add(next.get(0, Long.class)); + } + + ourLog.info("Sort PID order is now: {}", loadPids); + + pids.clear(); + pids.addAll(loadPids); + } + } + + IBundleProvider retVal = new IBundleProvider() { + + @Override + public InstantDt getPublished() { + return now; + } + + @Override + public List getResources(final int theFromIndex, final int theToIndex) { + TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + return template.execute(new TransactionCallback>() { + @Override + public List doInTransaction(TransactionStatus theStatus) { + List pidsSubList = pids.subList(theFromIndex, theToIndex); + + // Execute the query and make sure we return distinct results + List retVal = new ArrayList(); + loadResourcesByPid(pidsSubList, retVal, BundleEntrySearchModeEnum.MATCH); + + // Load _include resources + if (theParams.getIncludes() != null && theParams.getIncludes().isEmpty() == false) { + Set previouslyLoadedPids = new HashSet(); + + Set includePids = new HashSet(); + List resources = retVal; + do { + includePids.clear(); + + FhirTerser t = getContext().newTerser(); + for (Include next : theParams.getIncludes()) { + for (IResource nextResource : resources) { + RuntimeResourceDefinition def = getContext().getResourceDefinition(nextResource); + List values; + if ("*".equals(next.getValue())) { + values = new ArrayList(); + values.addAll(t.getAllPopulatedChildElementsOfType(nextResource, BaseResourceReferenceDt.class)); + } else if (next.getValue().startsWith(def.getName() + ".")) { + values = t.getValues(nextResource, next.getValue()); + } else { + continue; + } + + for (Object object : values) { + if (object == null) { + continue; + } + if (!(object instanceof BaseResourceReferenceDt)) { + throw new InvalidRequestException("Path '" + next.getValue() + "' produced non ResourceReferenceDt value: " + object.getClass()); + } + BaseResourceReferenceDt rr = (BaseResourceReferenceDt) object; + if (rr.getReference().isEmpty()) { + continue; + } + if (rr.getReference().isLocal()) { + continue; + } + + IdDt nextId = rr.getReference().toUnqualified(); + if (!previouslyLoadedPids.contains(nextId)) { + includePids.add(nextId); + previouslyLoadedPids.add(nextId); + } + } + } + } + + if (!includePids.isEmpty()) { + ourLog.info("Loading {} included resources", includePids.size()); + resources = loadResourcesById(includePids); + for (IResource next : resources) { + ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(next, BundleEntrySearchModeEnum.INCLUDE); + } + retVal.addAll(resources); + } + } while (includePids.size() > 0 && previouslyLoadedPids.size() < getConfig().getIncludeLimit()); + + if (previouslyLoadedPids.size() >= getConfig().getIncludeLimit()) { + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails("Not all _include resources were actually included as the request surpassed the limit of " + getConfig().getIncludeLimit() + " resources"); + retVal.add(0, oo); + } + } + + return retVal; + } + }); + } + + @Override + public int size() { + return pids.size(); + } + }; + + ourLog.info("Processed search for {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() }); + + return retVal; + } + + @Override + public IBundleProvider search(String theParameterName, IQueryParameterType theValue) { + return search(Collections.singletonMap(theParameterName, theValue)); + } + + @Override + public Set searchForIds(Map theParams) { + SearchParameterMap map = new SearchParameterMap(); + for (Entry nextEntry : theParams.entrySet()) { + map.add(nextEntry.getKey(), (nextEntry.getValue())); + } + return searchForIdsWithAndOr(map); + } + + @Override + public Set searchForIds(String theParameterName, IQueryParameterType theValue) { + return searchForIds(Collections.singletonMap(theParameterName, theValue)); + } + + @Override + public Set searchForIdsWithAndOr(SearchParameterMap theParams) { + SearchParameterMap params = theParams; + if (params == null) { + params = new SearchParameterMap(); + } + + RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceType); + + Set pids = new HashSet(); + + for (Entry>> nextParamEntry : params.entrySet()) { + String nextParamName = nextParamEntry.getKey(); + if (nextParamName.equals("_id")) { + + if (nextParamEntry.getValue().isEmpty()) { + continue; + } else if (nextParamEntry.getValue().size() > 1) { + throw new InvalidRequestException("AND queries not supported for _id (Multiple instances of this param found)"); + } else { + Set joinPids = new HashSet(); + List nextValue = nextParamEntry.getValue().get(0); + if (nextValue == null || nextValue.size() == 0) { + continue; + } else { + for (IQueryParameterType next : nextValue) { + String value = next.getValueAsQueryToken(); + IdDt valueId = new IdDt(value); + try { + long valueLong = translateForcedIdToPid(valueId); + joinPids.add(valueLong); + } catch (ResourceNotFoundException e) { + // This isn't an error, just means no result found + } + } + if (joinPids.isEmpty()) { + continue; + } + } + + pids = addPredicateId(pids, joinPids); + if (pids.isEmpty()) { + return new HashSet(); + } + + if (pids.isEmpty()) { + pids.addAll(joinPids); + } else { + pids.retainAll(joinPids); + } + } + + } else if (nextParamName.equals("_language")) { + + pids = addPredicateLanguage(pids, nextParamEntry.getValue()); + + } else { + + RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); + if (nextParamDef != null) { + switch (nextParamDef.getParamType()) { + case DATE: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateDate(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case QUANTITY: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateQuantity(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case REFERENCE: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateReference(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case STRING: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateString(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case TOKEN: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateToken(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case NUMBER: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateNumber(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case COMPOSITE: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateComposite(nextParamDef, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + } + } + } + } + + return pids; + } + + @SuppressWarnings("unchecked") + @Required + public void setResourceType(Class theTableType) { + myResourceType = (Class) theTableType; + } + + /** + * If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to + * share the same value. + */ + public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) { + mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName; + } + + private DaoMethodOutcome toMethodOutcome(final ResourceTable theEntity, IResource theResource) { + DaoMethodOutcome outcome = new DaoMethodOutcome(); + outcome.setId(theEntity.getIdDt()); + outcome.setEntity(theEntity); + outcome.setResource(theResource); + if (theResource != null) { + theResource.setId(theEntity.getIdDt()); + } return outcome; } @@ -1542,11 +1566,57 @@ public class FhirResourceDao extends BaseFhirDao implements return qp; } + @Override + public DaoMethodOutcome update(T theResource) { + return update(theResource,null); + } + + @Override + public DaoMethodOutcome update(T theResource, String theMatchUrl) { + return update(theResource, theMatchUrl, true); + } + + @Override + public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing) { + StopWatch w = new StopWatch(); + + final ResourceTable entity; + + IdDt resourceId; + if (isNotBlank(theMatchUrl)) { + Set match = processMatchUrl(theMatchUrl, myResourceType); + if (match.size() > 1) { + String msg = getContext().getLocalizer().getMessage(BaseFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size()); + throw new PreconditionFailedException(msg); + } else if (match.size() == 1) { + Long pid = match.iterator().next(); + entity = myEntityManager.find(ResourceTable.class, pid); + resourceId = entity.getIdDt(); + } else { + return create(theResource); + } + } else { + resourceId = theResource.getId(); + entity = readEntityLatestVersion(resourceId); + } + + if (resourceId.hasVersionIdPart() && resourceId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { + throw new InvalidRequestException("Trying to update " + resourceId + " but this is not the current version"); + } + + ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true); + + notifyWriteCompleted(); + ourLog.info("Processed update on {} in {}ms", resourceId, w.getMillisAndRestart()); + return toMethodOutcome(savedEntity, theResource).setCreated(false); + } + private void validateGivenIdIsAppropriateToRetrieveResource(IdDt theId, BaseHasResource entity) { if (entity.getForcedId() != null) { if (theId.isIdPartValidLong()) { // This means that the resource with the given numeric ID exists, but it has a "forced ID", meaning that - // as far as the outside world is concerned, the given ID doesn't exist (it's just an internal pointer to the + // as far as the outside world is concerned, the given ID doesn't exist (it's just an internal pointer + // to the // forced ID) throw new ResourceNotFoundException(theId); } @@ -1555,8 +1625,7 @@ public class FhirResourceDao extends BaseFhirDao implements private void validateResourceType(BaseHasResource entity) { if (!myResourceName.equals(entity.getResourceType())) { - throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " - + entity.getResourceType()); + throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " + entity.getResourceType()); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java index 639e81b49be..ba5bb94376d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java @@ -20,229 +20,516 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import static org.apache.commons.lang3.StringUtils.*; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; +import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryTransactionResponse; +import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; +import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; +import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; +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.util.FhirTerser; public class FhirSystemDaoDstu2 extends BaseFhirSystemDao { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2.class); + private UrlParts parseUrl(String theAction, String theUrl) { + UrlParts retVal = new UrlParts(); + + //@formatter:off + /* + * We assume that the URL passed in is in one of the following forms: + * [Resource Type]?[Search Params] + * [Resource Type]/[Resource ID] + * [Resource Type]/[Resource ID]/_history/[Version ID] + */ + //@formatter:on + int nextStart = 0; + boolean nextIsHistory = false; + + for (int idx = 0; idx < theUrl.length(); idx++) { + char nextChar = theUrl.charAt(idx); + boolean atEnd = (idx + 1) == theUrl.length(); + if (nextChar == '?' || nextChar == '/' || atEnd) { + int endIdx = atEnd ? idx + 1 : idx; + String nextSubstring = theUrl.substring(nextStart, endIdx); + if (retVal.getResourceType() == null) { + retVal.setResourceType(nextSubstring); + } else if (retVal.getResourceId() == null) { + retVal.setResourceId(nextSubstring); + } else if (nextIsHistory) { + retVal.setVersionId(nextSubstring); + } else { + if (nextSubstring.equals(Constants.URL_TOKEN_HISTORY)) { + nextIsHistory = true; + } else { + String msg = getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl); + throw new InvalidRequestException(msg); + } + } + if (nextChar == '?') { + if (theUrl.length() > idx + 1) { + retVal.setParams(theUrl.substring(idx + 1, theUrl.length())); + } + break; + } + nextStart = idx + 1; + } + } + + RuntimeResourceDefinition resType = getContext().getResourceDefinition(retVal.getResourceType()); + IFhirResourceDao dao = null; + if (resType != null) { + dao = getDao(resType.getImplementingClass()); + } + if (dao == null) { + String msg = getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl); + throw new InvalidRequestException(msg); + } + retVal.setDao(dao); + + if (retVal.getResourceId() == null && retVal.getParams() == null) { + String msg = getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl); + throw new InvalidRequestException(msg); + } + + return retVal; + } + + @SuppressWarnings("unchecked") @Transactional(propagation = Propagation.REQUIRED) @Override public Bundle transaction(Bundle theResources) { ourLog.info("Beginning transaction with {} resources", theResources.getEntry().size()); long start = System.currentTimeMillis(); -// Set allIds = new HashSet(); -// -// for (int i = 0; i < theResources.size(); i++) { -// IResource res = theResources.get(i); -// if (res.getId().hasIdPart() && !res.getId().hasResourceType()) { -// res.setId(new IdDt(toResourceName(res.getClass()), res.getId().getIdPart())); -// } -// -// /* -// * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness -// */ -// if (res.getId().hasResourceType() && res.getId().hasIdPart()) { -// IdDt nextId = res.getId().toUnqualifiedVersionless(); -// if (!allIds.add(nextId)) { -// throw new InvalidRequestException("Transaction bundle contains multiple resources with ID: " + nextId); -// } -// } -// } -// -// FhirTerser terser = getContext().newTerser(); -// -// int creations = 0; -// int updates = 0; -// -// Map idConversions = new HashMap(); -// -// List persistedResources = new ArrayList(); -// -// List retVal = new ArrayList(); -// OperationOutcome oo = new OperationOutcome(); -// retVal.add(oo); -// -// for (int resourceIdx = 0; resourceIdx < theResources.size(); resourceIdx++) { -// IResource nextResource = theResources.get(resourceIdx); -// -// IdDt nextId = nextResource.getId(); -// if (nextId == null) { -// nextId = new IdDt(); -// } -// -// String resourceName = toResourceName(nextResource); -// BundleEntryTransactionOperationEnum nextResouceOperationIn = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(nextResource); -// if (nextResouceOperationIn == null && hasValue(ResourceMetadataKeyEnum.DELETED_AT.get(nextResource))) { -// nextResouceOperationIn = BundleEntryTransactionOperationEnum.DELETE; -// } -// -// String matchUrl = ResourceMetadataKeyEnum.LINK_SEARCH.get(nextResource); -// Set candidateMatches = null; -// if (StringUtils.isNotBlank(matchUrl)) { -// candidateMatches = processMatchUrl(matchUrl, nextResource.getClass()); -// } -// -// ResourceTable entity; -// if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.CREATE) { -// entity = null; -// } else if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.UPDATE || nextResouceOperationIn == BundleEntryTransactionOperationEnum.DELETE) { -// if (candidateMatches == null || candidateMatches.size() == 0) { -// if (nextId == null || StringUtils.isBlank(nextId.getIdPart())) { -// throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, "transactionOperationFailedNoId", nextResouceOperationIn.name())); -// } -// entity = tryToLoadEntity(nextId); -// if (entity == null) { -// if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.UPDATE) { -// ourLog.debug("Attempting to UPDATE resource with unknown ID '{}', will CREATE instead", nextId); -// } else if (candidateMatches == null) { -// throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, "transactionOperationFailedUnknownId", nextResouceOperationIn.name(), nextId)); -// } else { -// ourLog.debug("Resource with match URL [{}] already exists, will be NOOP", matchUrl); -// ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(nextResource, BundleEntryTransactionOperationEnum.NOOP); -// persistedResources.add(null); -// retVal.add(nextResource); -// continue; -// } -// } -// } else if (candidateMatches.size() == 1) { -// entity = loadFirstEntityFromCandidateMatches(candidateMatches); -// } else { -// throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, "transactionOperationWithMultipleMatchFailure", nextResouceOperationIn.name(), matchUrl, candidateMatches.size())); -// } -// } else if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.NOOP) { -// throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, "incomingNoopInTransaction")); -// } else if (nextId.isEmpty()) { -// entity = null; -// } else { -// entity = tryToLoadEntity(nextId); -// } -// -// BundleEntryTransactionOperationEnum nextResouceOperationOut; -// if (entity == null) { -// nextResouceOperationOut = BundleEntryTransactionOperationEnum.CREATE; -// entity = toEntity(nextResource); -// if (nextId.isEmpty() == false && nextId.getIdPart().startsWith("cid:")) { -// ourLog.debug("Resource in transaction has ID[{}], will replace with server assigned ID", nextId.getIdPart()); -// } else if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.CREATE) { -// if (nextId.isEmpty() == false) { -// ourLog.debug("Resource in transaction has ID[{}] but is marked for CREATE, will ignore ID", nextId.getIdPart()); -// } -// if (candidateMatches != null) { -// if (candidateMatches.size() == 1) { -// ourLog.debug("Resource with match URL [{}] already exists, will be NOOP", matchUrl); -// BaseHasResource existingEntity = loadFirstEntityFromCandidateMatches(candidateMatches); -// IResource existing = (IResource) toResource(existingEntity); -// ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(existing, BundleEntryTransactionOperationEnum.NOOP); -// persistedResources.add(null); -// retVal.add(existing); -// continue; -// } -// if (candidateMatches.size() > 1) { -// throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, "transactionOperationWithMultipleMatchFailure", BundleEntryTransactionOperationEnum.CREATE.name(), matchUrl, candidateMatches.size())); -// } -// } -// } else { -// createForcedIdIfNeeded(entity, nextId); -// } -// myEntityManager.persist(entity); -// if (entity.getForcedId() != null) { -// myEntityManager.persist(entity.getForcedId()); -// } -// creations++; -// ourLog.info("Resource Type[{}] with ID[{}] does not exist, creating it", resourceName, nextId); -// } else { -// nextResouceOperationOut = nextResouceOperationIn; -// if (nextResouceOperationOut == null) { -// nextResouceOperationOut = BundleEntryTransactionOperationEnum.UPDATE; -// } -// updates++; -// ourLog.info("Resource Type[{}] with ID[{}] exists, updating it", resourceName, nextId); -// } -// -// persistedResources.add(entity); -// retVal.add(nextResource); -// ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(nextResource, nextResouceOperationOut); -// } -// -// ourLog.info("Flushing transaction to database"); -// myEntityManager.flush(); -// -// for (int i = 0; i < persistedResources.size(); i++) { -// ResourceTable entity = persistedResources.get(i); -// -// String resourceName = toResourceName(theResources.get(i)); -// IdDt nextId = theResources.get(i).getId(); -// -// IdDt newId; -// -// if (entity == null) { -// newId = retVal.get(i + 1).getId().toUnqualifiedVersionless(); -// } else { -// newId = entity.getIdDt().toUnqualifiedVersionless(); -// } -// -// if (nextId == null || nextId.isEmpty()) { -// ourLog.info("Transaction resource (with no preexisting ID) has been assigned new ID[{}]", nextId, newId); -// } else { -// if (nextId.toUnqualifiedVersionless().equals(newId)) { -// ourLog.info("Transaction resource ID[{}] is being updated", newId); -// } else { -// if (!nextId.getIdPart().startsWith("#")) { -// nextId = new IdDt(resourceName + '/' + nextId.getIdPart()); -// ourLog.info("Transaction resource ID[{}] has been assigned new ID[{}]", nextId, newId); -// idConversions.put(nextId, newId); -// } -// } -// } -// -// } -// -// for (IResource nextResource : theResources) { -// List allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, BaseResourceReferenceDt.class); -// for (BaseResourceReferenceDt nextRef : allRefs) { -// IdDt nextId = nextRef.getReference(); -// if (idConversions.containsKey(nextId)) { -// IdDt newId = idConversions.get(nextId); -// ourLog.info(" * Replacing resource ref {} with {}", nextId, newId); -// nextRef.setReference(newId); -// } else { -// ourLog.debug(" * Reference [{}] does not exist in bundle", nextId); -// } -// } -// } -// -// ourLog.info("Re-flushing updated resource references and extracting search criteria"); -// -// for (int i = 0; i < theResources.size(); i++) { -// IResource resource = theResources.get(i); -// ResourceTable table = persistedResources.get(i); -// if (table == null) { -// continue; -// } -// -// InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(resource); -// Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; -// if (deletedInstantOrNull == null && ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(resource) == BundleEntryTransactionOperationEnum.DELETE) { -// deletedTimestampOrNull = new Date(); -// ResourceMetadataKeyEnum.DELETED_AT.put(resource, new InstantDt(deletedTimestampOrNull)); -// } -// -// updateEntity(resource, table, table.getId() != null, deletedTimestampOrNull); -// } -// -// long delay = System.currentTimeMillis() - start; -// ourLog.info("Transaction completed in {}ms with {} creations and {} updates", new Object[] { delay, creations, updates }); -// -// oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Transaction completed in " + delay + "ms with " + creations + " creations and " + updates + " updates"); -// -// notifyWriteCompleted(); + Set allIds = new HashSet(); + Map idSubstitutions = new HashMap(); + Map idToPersistedOutcome = new HashMap(); - return null; + Bundle response = new Bundle(); + OperationOutcome oo = new OperationOutcome(); + response.addEntry().setResource(oo); + + for (int i = 0; i < theResources.getEntry().size(); i++) { + Entry nextEntry = theResources.getEntry().get(i); + IResource res = nextEntry.getResource(); + IdDt nextResourceId = null; + if (res != null) { + + nextResourceId = res.getId(); + if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType()) { + nextResourceId = new IdDt(toResourceName(res.getClass()), nextResourceId.getIdPart()); + res.setId(nextResourceId); + } + + /* + * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness + */ + if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) { + IdDt nextId = nextResourceId.toUnqualifiedVersionless(); + if (!allIds.add(nextId)) { + throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId)); + } + } + + } + + HTTPVerbEnum verb = nextEntry.getTransaction().getMethodElement().getValueAsEnum(); + if (verb == null) { + throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionEntryHasInvalidVerb", nextEntry.getTransaction().getMethod())); + } + + switch (verb) { + case POST: { + // CREATE + @SuppressWarnings("rawtypes") + IFhirResourceDao resourceDao = getDao(res.getClass()); + res.setId(null); + DaoMethodOutcome outcome; + Entry newEntry = response.addEntry(); + outcome = resourceDao.create(res, nextEntry.getTransaction().getIfNoneExist(), false); + handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry); + break; + } + case DELETE: { + // DELETE + Entry newEntry = response.addEntry(); + String url = extractTransactionUrlOrThrowException(nextEntry, verb); + UrlParts parts = parseUrl(verb.getCode(), url); + if (parts.getResourceId() != null) { + parts.getDao().delete(new IdDt(parts.getResourceType(), parts.getResourceId())); + } else { + parts.getDao().deleteByUrl(parts.getResourceType() + '?' + parts.getParams()); + } + + newEntry.getTransactionResponse().setStatus(Integer.toString(Constants.STATUS_HTTP_204_NO_CONTENT)); + break; + } + case PUT: { + // UPDATE + @SuppressWarnings("rawtypes") + IFhirResourceDao resourceDao = getDao(res.getClass()); + + DaoMethodOutcome outcome; + Entry newEntry = response.addEntry(); + + String url = extractTransactionUrlOrThrowException(nextEntry, verb); + + UrlParts parts = parseUrl(verb.getCode(), url); + if (parts.getResourceId() != null) { + res.setId(new IdDt(parts.getResourceType(), parts.getResourceId())); + outcome = resourceDao.update(res, null, false); + } else { + res.setId(null); + outcome = resourceDao.update(res, parts.getResourceType() + '?' + parts.getParams(), false); + } + + handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry); + break; + } + case GET: { + // SEARCH/READ/VREAD + String url = extractTransactionUrlOrThrowException(nextEntry, verb); + UrlParts parts = parseUrl(verb.getCode(), url); + + @SuppressWarnings("rawtypes") + IFhirResourceDao resourceDao = parts.getDao(); + + if (parts.getResourceId() != null && parts.getParams() == null) { + IResource found; + if (parts.getVersionId() != null) { + found = resourceDao.read(new IdDt(parts.getResourceType(), parts.getResourceId(), parts.getVersionId())); + } else { + found = resourceDao.read(new IdDt(parts.getResourceType(), parts.getResourceId())); + } + EntryTransactionResponse resp = response.addEntry().setResource(found).getTransactionResponse(); + resp.setLocation(found.getId().toUnqualified().getValue()); + resp.addEtag(found.getId().getVersionIdPart()); + } else if (parts.getParams() != null) { + RuntimeResourceDefinition def = getContext().getResourceDefinition(parts.getDao().getResourceType()); + SearchParameterMap params = translateMatchUrl(url, def); + IBundleProvider bundle = parts.getDao().search(params); + + Bundle searchBundle = new Bundle(); + searchBundle.setTotal(bundle.size()); + + int configuredMax = 100; // this should probably be configurable or something + if (bundle.size() > configuredMax) { + oo.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails("Search nested within transaction found more than " + configuredMax + " matches, but paging is not supported in nested transactions"); + } + List resourcesToAdd = bundle.getResources(0, Math.min(bundle.size(), configuredMax)); + for (IResource next : resourcesToAdd) { + searchBundle.addEntry().setResource(next); + } + + response.addEntry().setResource(searchBundle); + } + } + } + + } + + FhirTerser terser = getContext().newTerser(); + + // int creations = 0; + // int updates = 0; + // + // Map idConversions = new HashMap(); + // + // List persistedResources = new ArrayList(); + // + // List retVal = new ArrayList(); + // OperationOutcome oo = new OperationOutcome(); + // retVal.add(oo); + // + // for (int resourceIdx = 0; resourceIdx < theResources.size(); resourceIdx++) { + // IResource nextResource = theResources.get(resourceIdx); + // + // IdDt nextId = nextResource.getId(); + // if (nextId == null) { + // nextId = new IdDt(); + // } + // + // String resourceName = toResourceName(nextResource); + // BundleEntryTransactionOperationEnum nextResouceOperationIn = + // ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(nextResource); + // if (nextResouceOperationIn == null && hasValue(ResourceMetadataKeyEnum.DELETED_AT.get(nextResource))) { + // nextResouceOperationIn = BundleEntryTransactionOperationEnum.DELETE; + // } + // + // String matchUrl = ResourceMetadataKeyEnum.LINK_SEARCH.get(nextResource); + // Set candidateMatches = null; + // if (StringUtils.isNotBlank(matchUrl)) { + // candidateMatches = processMatchUrl(matchUrl, nextResource.getClass()); + // } + // + // ResourceTable entity; + // if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.CREATE) { + // entity = null; + // } else if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.UPDATE || nextResouceOperationIn == + // BundleEntryTransactionOperationEnum.DELETE) { + // if (candidateMatches == null || candidateMatches.size() == 0) { + // if (nextId == null || StringUtils.isBlank(nextId.getIdPart())) { + // throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, + // "transactionOperationFailedNoId", nextResouceOperationIn.name())); + // } + // entity = tryToLoadEntity(nextId); + // if (entity == null) { + // if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.UPDATE) { + // ourLog.debug("Attempting to UPDATE resource with unknown ID '{}', will CREATE instead", nextId); + // } else if (candidateMatches == null) { + // throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, + // "transactionOperationFailedUnknownId", nextResouceOperationIn.name(), nextId)); + // } else { + // ourLog.debug("Resource with match URL [{}] already exists, will be NOOP", matchUrl); + // ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(nextResource, + // BundleEntryTransactionOperationEnum.NOOP); + // persistedResources.add(null); + // retVal.add(nextResource); + // continue; + // } + // } + // } else if (candidateMatches.size() == 1) { + // entity = loadFirstEntityFromCandidateMatches(candidateMatches); + // } else { + // throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, + // "transactionOperationWithMultipleMatchFailure", nextResouceOperationIn.name(), matchUrl, + // candidateMatches.size())); + // } + // } else if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.NOOP) { + // throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, + // "incomingNoopInTransaction")); + // } else if (nextId.isEmpty()) { + // entity = null; + // } else { + // entity = tryToLoadEntity(nextId); + // } + // + // BundleEntryTransactionOperationEnum nextResouceOperationOut; + // if (entity == null) { + // nextResouceOperationOut = BundleEntryTransactionOperationEnum.CREATE; + // entity = toEntity(nextResource); + // if (nextId.isEmpty() == false && nextId.getIdPart().startsWith("cid:")) { + // ourLog.debug("Resource in transaction has ID[{}], will replace with server assigned ID", nextId.getIdPart()); + // } else if (nextResouceOperationIn == BundleEntryTransactionOperationEnum.CREATE) { + // if (nextId.isEmpty() == false) { + // ourLog.debug("Resource in transaction has ID[{}] but is marked for CREATE, will ignore ID", + // nextId.getIdPart()); + // } + // if (candidateMatches != null) { + // if (candidateMatches.size() == 1) { + // ourLog.debug("Resource with match URL [{}] already exists, will be NOOP", matchUrl); + // BaseHasResource existingEntity = loadFirstEntityFromCandidateMatches(candidateMatches); + // IResource existing = (IResource) toResource(existingEntity); + // ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(existing, BundleEntryTransactionOperationEnum.NOOP); + // persistedResources.add(null); + // retVal.add(existing); + // continue; + // } + // if (candidateMatches.size() > 1) { + // throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirSystemDaoDstu2.class, + // "transactionOperationWithMultipleMatchFailure", BundleEntryTransactionOperationEnum.CREATE.name(), matchUrl, + // candidateMatches.size())); + // } + // } + // } else { + // createForcedIdIfNeeded(entity, nextId); + // } + // myEntityManager.persist(entity); + // if (entity.getForcedId() != null) { + // myEntityManager.persist(entity.getForcedId()); + // } + // creations++; + // ourLog.info("Resource Type[{}] with ID[{}] does not exist, creating it", resourceName, nextId); + // } else { + // nextResouceOperationOut = nextResouceOperationIn; + // if (nextResouceOperationOut == null) { + // nextResouceOperationOut = BundleEntryTransactionOperationEnum.UPDATE; + // } + // updates++; + // ourLog.info("Resource Type[{}] with ID[{}] exists, updating it", resourceName, nextId); + // } + // + // persistedResources.add(entity); + // retVal.add(nextResource); + // ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(nextResource, nextResouceOperationOut); + // } + // + // ourLog.info("Flushing transaction to database"); + // myEntityManager.flush(); + // + // for (int i = 0; i < persistedResources.size(); i++) { + // ResourceTable entity = persistedResources.get(i); + // + // String resourceName = toResourceName(theResources.get(i)); + // IdDt nextId = theResources.get(i).getId(); + // + // IdDt newId; + // + // if (entity == null) { + // newId = retVal.get(i + 1).getId().toUnqualifiedVersionless(); + // } else { + // newId = entity.getIdDt().toUnqualifiedVersionless(); + // } + // + // if (nextId == null || nextId.isEmpty()) { + // ourLog.info("Transaction resource (with no preexisting ID) has been assigned new ID[{}]", nextId, newId); + // } else { + // if (nextId.toUnqualifiedVersionless().equals(newId)) { + // ourLog.info("Transaction resource ID[{}] is being updated", newId); + // } else { + // if (!nextId.getIdPart().startsWith("#")) { + // nextId = new IdDt(resourceName + '/' + nextId.getIdPart()); + // ourLog.info("Transaction resource ID[{}] has been assigned new ID[{}]", nextId, newId); + // idConversions.put(nextId, newId); + // } + // } + // } + // + // } + // + for (DaoMethodOutcome nextOutcome : idToPersistedOutcome.values()) { + IResource nextResource = nextOutcome.getResource(); + if (nextResource == null) { + continue; + } + + List allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, BaseResourceReferenceDt.class); + for (BaseResourceReferenceDt nextRef : allRefs) { + IdDt nextId = nextRef.getReference(); + if (idSubstitutions.containsKey(nextId)) { + IdDt newId = idSubstitutions.get(nextId); + ourLog.info(" * Replacing resource ref {} with {}", nextId, newId); + nextRef.setReference(newId); + } else { + ourLog.debug(" * Reference [{}] does not exist in bundle", nextId); + } + } + + InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource); + Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; + updateEntity(nextResource, nextOutcome.getEntity(), false, deletedTimestampOrNull, true, false); + } + // + // ourLog.info("Re-flushing updated resource references and extracting search criteria"); + // + // for (int i = 0; i < theResources.size(); i++) { + // IResource resource = theResources.get(i); + // ResourceTable table = persistedResources.get(i); + // if (table == null) { + // continue; + // } + // + // InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(resource); + // Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; + // if (deletedInstantOrNull == null && ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(resource) == + // BundleEntryTransactionOperationEnum.DELETE) { + // deletedTimestampOrNull = new Date(); + // ResourceMetadataKeyEnum.DELETED_AT.put(resource, new InstantDt(deletedTimestampOrNull)); + // } + // + // updateEntity(resource, table, table.getId() != null, deletedTimestampOrNull); + // } + + long delay = System.currentTimeMillis() - start; + ourLog.info("Transaction completed in {}ms", new Object[] { delay }); + + oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Transaction completed in " + delay + "ms"); + + notifyWriteCompleted(); + + return response; } + private void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, Entry newEntry) { + IdDt newId = outcome.getId().toUnqualifiedVersionless(); + if (newId.equals(nextResourceId) == false) { + idSubstitutions.put(nextResourceId, newId); + } + idToPersistedOutcome.put(newId, outcome); + if (outcome.getCreated().booleanValue()) { + newEntry.getTransactionResponse().setStatus(Long.toString(Constants.STATUS_HTTP_201_CREATED)); + } else { + newEntry.getTransactionResponse().setStatus(Long.toString(Constants.STATUS_HTTP_200_OK)); + } + newEntry.getTransactionResponse().setLocation(outcome.getId().toUnqualified().getValue()); + newEntry.getTransactionResponse().addEtag().setValue(outcome.getId().getVersionIdPart()); + } + + private String extractTransactionUrlOrThrowException(Entry nextEntry, HTTPVerbEnum verb) { + String url = nextEntry.getTransaction().getUrl(); + if (isBlank(url)) { + throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionMissingUrl", verb.name())); + } + return url; + } + + private static class UrlParts { + private IFhirResourceDao myDao; + private String myParams; + private String myResourceId; + private String myResourceType; + private String myVersionId; + + public IFhirResourceDao getDao() { + return myDao; + } + + public void setVersionId(String theVersionId) { + myVersionId = theVersionId; + } + + public String getVersionId() { + return myVersionId; + } + + public String getParams() { + return myParams; + } + + public String getResourceId() { + return myResourceId; + } + + public String getResourceType() { + return myResourceType; + } + + public void setDao(IFhirResourceDao theDao) { + myDao = theDao; + } + + public void setParams(String theParams) { + myParams = theParams; + } + + public void setResourceId(String theResourceId) { + myResourceId = theResourceId; + } + + public void setResourceType(String theResourceType) { + myResourceType = theResourceType; + } + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java index 0e8c3de52dd..e5ac694c129 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java @@ -29,7 +29,6 @@ import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; @@ -37,9 +36,20 @@ public interface IFhirResourceDao extends IDao { void addTag(IdDt theId, String theScheme, String theTerm, String theLabel); - MethodOutcome create(T theResource); + DaoMethodOutcome create(T theResource); - MethodOutcome delete(IdDt theResource); + DaoMethodOutcome create(T theResource, String theIfNoneExist); + + /** + * @param thePerformIndexing + * Use with caution! If you set this to false, you need to manually perform indexing or your resources + * won't be indexed and searches won't work. + */ + DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing); + + DaoMethodOutcome delete(IdDt theResource); + + DaoMethodOutcome deleteByUrl(String theString); TagList getAllResourceTags(); @@ -49,20 +59,28 @@ public interface IFhirResourceDao extends IDao { IBundleProvider history(Date theSince); - IBundleProvider history(IdDt theId,Date theSince); + IBundleProvider history(IdDt theId, Date theSince); IBundleProvider history(Long theId, Date theSince); - + /** * * @param theId * @return - * @throws ResourceNotFoundException If the ID is not known to the server + * @throws ResourceNotFoundException + * If the ID is not known to the server */ T read(IdDt theId); BaseHasResource readEntity(IdDt theId); + /** + * @param theCheckForForcedId + * If true, this method should fail if the requested ID contains a numeric PID which exists, but is + * obscured by a "forced ID" so should not exist as far as the outside world is concerned. + */ + BaseHasResource readEntity(IdDt theId, boolean theCheckForForcedId); + void removeTag(IdDt theId, String theScheme, String theTerm); IBundleProvider search(Map theParams); @@ -75,15 +93,17 @@ public interface IFhirResourceDao extends IDao { Set searchForIds(String theParameterName, IQueryParameterType theValue); - MethodOutcome update(T theResource, IdDt theId); - Set searchForIdsWithAndOr(SearchParameterMap theParams); + DaoMethodOutcome update(T theResource); + + DaoMethodOutcome update(T theResource, String theMatchUrl); + /** - * @param theCheckForForcedId If true, this method should fail if the requested ID contains - * a numeric PID which exists, but is obscured by a "forced ID" so should not exist as - * far as the outside world is concerned. + * @param thePerformIndexing + * Use with caution! If you set this to false, you need to manually perform indexing or your resources + * won't be indexed and searches won't work. */ - BaseHasResource readEntity(IdDt theId, boolean theCheckForForcedId); - + DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java index ac32be24ccd..b459b0c1ca9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java @@ -32,9 +32,15 @@ import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.UniqueConstraint; +//@formatter:off @Entity() -@Table(name = "HFJ_FORCED_ID", uniqueConstraints = { @UniqueConstraint(name = "IDX_FORCEDID", columnNames = { "FORCED_ID" }) }) -@NamedQueries(@NamedQuery(name = "Q_GET_FORCED_ID", query = "SELECT f FROM ForcedId f WHERE myForcedId = :ID")) +@Table(name = "HFJ_FORCED_ID", uniqueConstraints = { + @UniqueConstraint(name = "IDX_FORCEDID", columnNames = { "FORCED_ID" }) +}) +@NamedQueries(value= { + @NamedQuery(name = "Q_GET_FORCED_ID", query = "SELECT f FROM ForcedId f WHERE myForcedId = :ID") +}) +//@formatter:on public class ForcedId { public static final int MAX_FORCED_ID_LENGTH = 100; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu1.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu1.java index 963adc166c5..802470045d5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu1.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu1.java @@ -27,6 +27,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.dstu.resource.Conformance.Rest; import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource; @@ -44,11 +45,11 @@ import javax.servlet.http.HttpServletRequest; public class JpaConformanceProviderDstu1 extends ServerConformanceProvider { private String myImplementationDescription; - private IFhirSystemDao mySystemDao; + private IFhirSystemDao> mySystemDao; private volatile Conformance myCachedValue; private RestfulServer myRestfulServer; - public JpaConformanceProviderDstu1(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao) { + public JpaConformanceProviderDstu1(RestfulServer theRestfulServer, IFhirSystemDao> theSystemDao) { super(theRestfulServer); myRestfulServer = theRestfulServer; mySystemDao = theSystemDao; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java index 015e32fa1f2..318bb8e52a1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java @@ -29,6 +29,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Conformance; import ca.uhn.fhir.model.dstu2.resource.Conformance.Rest; import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource; @@ -44,11 +45,11 @@ import ca.uhn.fhir.util.ExtensionConstants; public class JpaConformanceProviderDstu2 extends ServerConformanceProvider { private String myImplementationDescription; - private IFhirSystemDao mySystemDao; + private IFhirSystemDao mySystemDao; private volatile Conformance myCachedValue; private RestfulServer myRestfulServer; - public JpaConformanceProviderDstu2(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao) { + public JpaConformanceProviderDstu2(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao) { super(theRestfulServer); myRestfulServer = theRestfulServer; mySystemDao = theSystemDao; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProvider.java index b05fbe569cd..c9d91e27e10 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProvider.java @@ -162,13 +162,12 @@ public class JpaResourceProvider extends BaseJpaProvider im public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdDt theId) { startRequest(theRequest); try { - return myDao.update(theResource, theId); + theResource.setId(theId); + return myDao.update(theResource); } catch (ResourceNotFoundException e) { ourLog.info("Can't update resource with ID[" + theId.getValue() + "] because it doesn't exist, going to create it instead"); theResource.setId(theId); - MethodOutcome retVal = myDao.create(theResource); - retVal.setCreated(true); - return retVal; + return myDao.create(theResource); } finally { endRequest(theRequest); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java index a8c96cad46c..f16b60020fc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java @@ -1,17 +1,7 @@ package ca.uhn.fhir.jpa.dao; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; import java.util.ArrayList; import java.util.Date; @@ -40,6 +30,7 @@ import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.composite.QuantityDt; import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Device; import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport; import ca.uhn.fhir.model.dstu2.resource.Encounter; @@ -47,7 +38,9 @@ import ca.uhn.fhir.model.dstu2.resource.Location; import ca.uhn.fhir.model.dstu2.resource.Observation; import ca.uhn.fhir.model.dstu2.resource.Organization; import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum; +import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; import ca.uhn.fhir.model.dstu2.valueset.QuantityComparatorEnum; import ca.uhn.fhir.model.primitive.DateDt; import ca.uhn.fhir.model.primitive.DateTimeDt; @@ -65,8 +58,10 @@ import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; +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.UnprocessableEntityException; @@ -84,6 +79,150 @@ public class FhirResourceDaoTest { private static IFhirResourceDao ourOrganizationDao; private static IFhirResourceDao ourPatientDao; + @Test + public void testCreateDuplicateIdFails() { + String methodName = "testCreateDuplocateIdFailsText"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IdDt id = ourPatientDao.create(p).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); + try { + ourPatientDao.create(p); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Can not create entity with ID[" + methodName + "], a resource with this ID already exists")); + } + } + + @Test + public void testUpdateByUrl() { + String methodName = "testUpdateByUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IdDt id = ourPatientDao.create(p).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); + + ourPatientDao.update(p, "Patient?identifier=urn%3Asystem%7C" + methodName); + + p = ourPatientDao.read(id.toVersionless()); + assertThat(p.getId().toVersionless().toString(), not(containsString("test"))); + assertEquals(id.toVersionless(), p.getId().toVersionless()); + assertNotEquals(id, p.getId()); + assertThat(p.getId().toString(), endsWith("/_history/2")); + + } + + + @Test + public void testCreateNumericIdFails() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); + p.addName().addFamily("Hello"); + p.setId("Patient/123"); + try { + ourPatientDao.create(p); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Can not create entity with ID[123], this server does not allow clients to assign numeric IDs")); + } + } + + + @Test + public void testDeleteWithMatchUrl() { + String methodName = "testDeleteWithMatchUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IdDt id = ourPatientDao.create(p).getId(); + ourLog.info("Created patient, got it: {}", id); + + Bundle request = new Bundle(); + request.addEntry().setResource(p).getTransaction().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + ourPatientDao.deleteByUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + try { + ourPatientDao.read(id.toVersionless()); + fail(); + } catch (ResourceGoneException e) { + // ok + } + + try { + ourPatientDao.read(new IdDt("Patient/" + methodName)); + fail(); + } catch (ResourceNotFoundException e) { + // ok + } + + IBundleProvider history = ourPatientDao.history(id, null); + assertEquals(2, history.size()); + + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get(history.getResources(0, 0).get(0))); + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get(history.getResources(0, 0).get(0)).getValue()); + assertNull(ResourceMetadataKeyEnum.DELETED_AT.get(history.getResources(1,1).get(0))); + + } + + + @Test + public void testCreateWithIfNoneExist() { + String methodName = "testCreateWithIfNoneExist"; + MethodOutcome results; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IdDt id = ourPatientDao.create(p).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); + results = ourPatientDao.create(p, "Patient?identifier=urn%3Asystem%7C" + methodName); + assertEquals(id.getIdPart(), results.getId().getIdPart()); + assertFalse(results.getCreated().booleanValue()); + + // Now create a second one + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addFamily("Hello"); + p.setId("Patient/" + methodName + "DOESNTEXIST"); + results = ourPatientDao.create(p, "Patient?identifier=urn%3Asystem%7C" + methodName + "DOESNTEXIST"); + assertNotEquals(id.getIdPart(), results.getId().getIdPart()); + assertTrue(results.getCreated().booleanValue()); + + // Now try to create one with the original match URL and it should fail + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addFamily("Hello"); + p.setId("Patient/" + methodName); + try { + ourPatientDao.create(p, "Patient?identifier=urn%3Asystem%7C" + methodName); + fail(); + } catch (PreconditionFailedException e) { + assertThat(e.getMessage(), containsString("Failed to CREATE")); + } + + } + @Test public void testChoiceParamConcept() { Observation o1 = new Observation(); @@ -216,7 +355,7 @@ public class FhirResourceDaoTest { public void testDatePeriodParamStartAndEnd() { { Encounter enc = new Encounter(); - enc.addIdentifier().setSystem("testDatePeriodParam").setValue( "03"); + enc.addIdentifier().setSystem("testDatePeriodParam").setValue("03"); enc.getPeriod().getStartElement().setValueAsString("2001-01-02"); enc.getPeriod().getEndElement().setValueAsString("2001-01-03"); ourEncounterDao.create(enc); @@ -329,7 +468,7 @@ public class FhirResourceDaoTest { { Patient patient = ourPatientDao.read(id2); patient.addIdentifier().setSystem("ZZZZZZZ").setValue("ZZZZZZZZZ"); - id2b = ourPatientDao.update(patient, id2).getId(); + id2b = ourPatientDao.update(patient).getId(); } ourLog.info("ID1:{} ID2:{} ID2b:{}", new Object[] { id1, id2, id2b }); @@ -373,7 +512,7 @@ public class FhirResourceDaoTest { @Test public void testIdParam() { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addFamily("Tester").addGiven("Joe"); MethodOutcome outcome = ourPatientDao.create(patient); @@ -484,11 +623,11 @@ public class FhirResourceDaoTest { @Test public void testPersistResourceLink() { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "testPersistResourceLink01"); + patient.addIdentifier().setSystem("urn:system").setValue("testPersistResourceLink01"); IdDt patientId01 = ourPatientDao.create(patient).getId(); Patient patient02 = new Patient(); - patient02.addIdentifier().setSystem("urn:system").setValue( "testPersistResourceLink02"); + patient02.addIdentifier().setSystem("urn:system").setValue("testPersistResourceLink02"); IdDt patientId02 = ourPatientDao.create(patient02).getId(); Observation obs01 = new Observation(); @@ -524,7 +663,7 @@ public class FhirResourceDaoTest { @Test public void testPersistSearchParamDate() { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.setBirthDate(new DateDt("2001-01-01")); ourPatientDao.create(patient); @@ -576,7 +715,7 @@ public class FhirResourceDaoTest { @Test public void testPersistSearchParams() { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "001testPersistSearchParams"); + patient.addIdentifier().setSystem("urn:system").setValue("001testPersistSearchParams"); patient.getGenderElement().setValueAsEnum(AdministrativeGenderEnum.MALE); patient.addName().addFamily("Tester").addGiven("JoetestPersistSearchParams"); @@ -641,20 +780,20 @@ public class FhirResourceDaoTest { assertTrue(patients.size() >= 2); } - @Test public void testHistoryByForcedId() { IdDt idv1; IdDt idv2; { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "testHistoryByForcedId"); + patient.addIdentifier().setSystem("urn:system").setValue("testHistoryByForcedId"); patient.addName().addFamily("Tester").addGiven("testHistoryByForcedId"); patient.setId("Patient/testHistoryByForcedId"); idv1 = ourPatientDao.create(patient).getId(); patient.addName().addFamily("Tester").addGiven("testHistoryByForcedIdName2"); - idv2 = ourPatientDao.update(patient, idv1.toUnqualifiedVersionless()).getId(); + patient.setId(patient.getId().toUnqualifiedVersionless()); + idv2 = ourPatientDao.update(patient).getId(); } List patients = toList(ourPatientDao.history(idv1.toVersionless(), null)); @@ -676,7 +815,7 @@ public class FhirResourceDaoTest { IdDt id2; { Organization patient = new Organization(); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); id2 = ourOrganizationDao.create(patient).getId(); } @@ -769,14 +908,14 @@ public class FhirResourceDaoTest { IdDt id1; { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addFamily("testSearchNameParam01Fam").addGiven("testSearchNameParam01Giv"); ResourceMetadataKeyEnum.TITLE.put(patient, "P1TITLE"); id1 = ourPatientDao.create(patient).getId(); } { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "002"); + patient.addIdentifier().setSystem("urn:system").setValue("002"); patient.addName().addFamily("testSearchNameParam02Fam").addGiven("testSearchNameParam02Giv"); ourPatientDao.create(patient); } @@ -816,12 +955,12 @@ public class FhirResourceDaoTest { @Test public void testSearchNumberParam() { Encounter e1 = new Encounter(); - e1.addIdentifier().setSystem("foo").setValue( "testSearchNumberParam01"); + e1.addIdentifier().setSystem("foo").setValue("testSearchNumberParam01"); e1.getLength().setSystem(BaseFhirDao.UCUM_NS).setCode("min").setValue(4.0 * 24 * 60); IdDt id1 = ourEncounterDao.create(e1).getId(); Encounter e2 = new Encounter(); - e2.addIdentifier().setSystem("foo").setValue( "testSearchNumberParam02"); + e2.addIdentifier().setSystem("foo").setValue("testSearchNumberParam02"); e2.getLength().setSystem(BaseFhirDao.UCUM_NS).setCode("year").setValue(2.0); IdDt id2 = ourEncounterDao.create(e2).getId(); { @@ -843,13 +982,13 @@ public class FhirResourceDaoTest { @Test public void testSearchResourceLinkWithChain() { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "testSearchResourceLinkWithChainXX"); - patient.addIdentifier().setSystem("urn:system").setValue( "testSearchResourceLinkWithChain01"); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChainXX"); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChain01"); IdDt patientId01 = ourPatientDao.create(patient).getId(); Patient patient02 = new Patient(); - patient02.addIdentifier().setSystem("urn:system").setValue( "testSearchResourceLinkWithChainXX"); - patient02.addIdentifier().setSystem("urn:system").setValue( "testSearchResourceLinkWithChain02"); + patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChainXX"); + patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithChain02"); IdDt patientId02 = ourPatientDao.create(patient02).getId(); Observation obs01 = new Observation(); @@ -881,7 +1020,7 @@ public class FhirResourceDaoTest { result = toList(ourObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "testSearchResourceLinkWithChainXX"))); assertEquals(2, result.size()); - + result = toList(ourObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "|testSearchResourceLinkWithChainXX"))); assertEquals(0, result.size()); @@ -891,14 +1030,14 @@ public class FhirResourceDaoTest { public void testSearchResourceLinkWithTextLogicalId() { Patient patient = new Patient(); patient.setId("testSearchResourceLinkWithTextLogicalId01"); - patient.addIdentifier().setSystem("urn:system").setValue( "testSearchResourceLinkWithTextLogicalIdXX"); - patient.addIdentifier().setSystem("urn:system").setValue( "testSearchResourceLinkWithTextLogicalId01"); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalIdXX"); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalId01"); IdDt patientId01 = ourPatientDao.create(patient).getId(); Patient patient02 = new Patient(); patient02.setId("testSearchResourceLinkWithTextLogicalId02"); - patient02.addIdentifier().setSystem("urn:system").setValue( "testSearchResourceLinkWithTextLogicalIdXX"); - patient02.addIdentifier().setSystem("urn:system").setValue( "testSearchResourceLinkWithTextLogicalId02"); + patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalIdXX"); + patient02.addIdentifier().setSystem("urn:system").setValue("testSearchResourceLinkWithTextLogicalId02"); IdDt patientId02 = ourPatientDao.create(patient02).getId(); Observation obs01 = new Observation(); @@ -981,13 +1120,13 @@ public class FhirResourceDaoTest { public void testSearchStringParam() { { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addFamily("Tester_testSearchStringParam").addGiven("Joe"); ourPatientDao.create(patient); } { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "002"); + patient.addIdentifier().setSystem("urn:system").setValue("002"); patient.addName().addFamily("Tester_testSearchStringParam").addGiven("John"); ourPatientDao.create(patient); } @@ -1009,7 +1148,7 @@ public class FhirResourceDaoTest { { Patient patient = new Patient(); patient.getLanguage().setValue("en_CA"); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addFamily("testSearchLanguageParam").addGiven("Joe"); id1 = ourPatientDao.create(patient).getId(); } @@ -1017,7 +1156,7 @@ public class FhirResourceDaoTest { { Patient patient = new Patient(); patient.getLanguage().setValue("en_US"); - patient.addIdentifier().setSystem("urn:system").setValue( "002"); + patient.addIdentifier().setSystem("urn:system").setValue("002"); patient.addName().addFamily("testSearchLanguageParam").addGiven("John"); id2 = ourPatientDao.create(patient).getId(); } @@ -1048,13 +1187,13 @@ public class FhirResourceDaoTest { public void testSearchStringParamWithNonNormalized() { { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addGiven("testSearchStringParamWithNonNormalized_h\u00F6ra"); ourPatientDao.create(patient); } { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "002"); + patient.addIdentifier().setSystem("urn:system").setValue("002"); patient.addName().addGiven("testSearchStringParamWithNonNormalized_HORA"); ourPatientDao.create(patient); } @@ -1081,7 +1220,7 @@ public class FhirResourceDaoTest { ourPatientDao.create(patient); patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "testSearchTokenParam002"); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam002"); patient.addName().addFamily("Tester").addGiven("testSearchTokenParam2"); ourPatientDao.create(patient); @@ -1147,14 +1286,14 @@ public class FhirResourceDaoTest { IdDt orgId = ourOrganizationDao.create(org).getId(); Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addFamily("Tester_testSearchWithIncludes_P1").addGiven("Joe"); patient.getManagingOrganization().setReference(orgId); ourPatientDao.create(patient); } { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "002"); + patient.addIdentifier().setSystem("urn:system").setValue("002"); patient.addName().addFamily("Tester_testSearchWithIncludes_P2").addGiven("John"); ourPatientDao.create(patient); } @@ -1221,7 +1360,7 @@ public class FhirResourceDaoTest { public void testStoreUtf8Characters() throws Exception { Organization org = new Organization(); org.setName("測試醫院"); - org.addIdentifier().setSystem("urn:system").setValue( "testStoreUtf8Characters_01"); + org.addIdentifier().setSystem("urn:system").setValue("testStoreUtf8Characters_01"); IdDt orgId = ourOrganizationDao.create(org).getId(); Organization returned = ourOrganizationDao.read(orgId); @@ -1244,14 +1383,14 @@ public class FhirResourceDaoTest { assertThat(orgId.getValue(), endsWith("Organization/testSearchWithIncludesThatHaveTextId_id1/_history/1")); Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addFamily("Tester_testSearchWithIncludesThatHaveTextId_P1").addGiven("Joe"); patient.getManagingOrganization().setReference(orgId); ourPatientDao.create(patient); } { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "002"); + patient.addIdentifier().setSystem("urn:system").setValue("002"); patient.addName().addFamily("Tester_testSearchWithIncludesThatHaveTextId_P2").addGiven("John"); ourPatientDao.create(patient); } @@ -1290,23 +1429,23 @@ public class FhirResourceDaoTest { @Test public void testSort() { Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue( "testSort001"); + p.addIdentifier().setSystem("urn:system").setValue("testSort001"); p.addName().addFamily("testSortF1").addGiven("testSortG1"); IdDt id1 = ourPatientDao.create(p).getId().toUnqualifiedVersionless(); // Create out of order p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue( "testSort001"); + p.addIdentifier().setSystem("urn:system").setValue("testSort001"); p.addName().addFamily("testSortF3").addGiven("testSortG3"); IdDt id3 = ourPatientDao.create(p).getId().toUnqualifiedVersionless(); p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue( "testSort001"); + p.addIdentifier().setSystem("urn:system").setValue("testSort001"); p.addName().addFamily("testSortF2").addGiven("testSortG2"); IdDt id2 = ourPatientDao.create(p).getId().toUnqualifiedVersionless(); p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue( "testSort001"); + p.addIdentifier().setSystem("urn:system").setValue("testSort001"); IdDt id4 = ourPatientDao.create(p).getId().toUnqualifiedVersionless(); SearchParameterMap pm = new SearchParameterMap(); @@ -1367,7 +1506,7 @@ public class FhirResourceDaoTest { @Test public void testTagsWithCreateAndReadAndSearch() { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "testTagsWithCreateAndReadAndSearch"); + patient.addIdentifier().setSystem("urn:system").setValue("testTagsWithCreateAndReadAndSearch"); patient.addName().addFamily("Tester").addGiven("Joe"); TagList tagList = new TagList(); tagList.addTag(null, "Dog", "Puppies"); @@ -1458,7 +1597,7 @@ public class FhirResourceDaoTest { @Test public void testUpdateAndGetHistoryResource() throws InterruptedException { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue( "001"); + patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addFamily("Tester").addGiven("Joe"); MethodOutcome outcome = ourPatientDao.create(patient); @@ -1477,7 +1616,7 @@ public class FhirResourceDaoTest { Thread.sleep(1000); retrieved.getIdentifierFirstRep().setValue("002"); - MethodOutcome outcome2 = ourPatientDao.update(retrieved, outcome.getId()); + MethodOutcome outcome2 = ourPatientDao.update(retrieved); assertEquals(outcome.getId().getIdPart(), outcome2.getId().getIdPart()); assertNotEquals(outcome.getId().getVersionIdPart(), outcome2.getId().getVersionIdPart()); @@ -1521,12 +1660,12 @@ public class FhirResourceDaoTest { @Test public void testUpdateMaintainsSearchParams() throws InterruptedException { Patient p1 = new Patient(); - p1.addIdentifier().setSystem("urn:system").setValue( "testUpdateMaintainsSearchParamsAAA"); + p1.addIdentifier().setSystem("urn:system").setValue("testUpdateMaintainsSearchParamsAAA"); p1.addName().addFamily("Tester").addGiven("testUpdateMaintainsSearchParamsAAA"); IdDt p1id = ourPatientDao.create(p1).getId(); Patient p2 = new Patient(); - p2.addIdentifier().setSystem("urn:system").setValue( "testUpdateMaintainsSearchParamsBBB"); + p2.addIdentifier().setSystem("urn:system").setValue("testUpdateMaintainsSearchParamsBBB"); p2.addName().addFamily("Tester").addGiven("testUpdateMaintainsSearchParamsBBB"); ourPatientDao.create(p2).getId(); @@ -1536,7 +1675,7 @@ public class FhirResourceDaoTest { // Update the name p1.getNameFirstRep().getGivenFirstRep().setValue("testUpdateMaintainsSearchParamsBBB"); - MethodOutcome update2 = ourPatientDao.update(p1, p1id); + MethodOutcome update2 = ourPatientDao.update(p1); IdDt p1id2 = update2.getId(); ids = ourPatientDao.searchForIds(Patient.SP_GIVEN, new StringDt("testUpdateMaintainsSearchParamsAAA")); @@ -1557,21 +1696,23 @@ public class FhirResourceDaoTest { @Test public void testUpdateRejectsInvalidTypes() throws InterruptedException { Patient p1 = new Patient(); - p1.addIdentifier().setSystem("urn:system").setValue( "testUpdateRejectsInvalidTypes"); + p1.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsInvalidTypes"); p1.addName().addFamily("Tester").addGiven("testUpdateRejectsInvalidTypes"); IdDt p1id = ourPatientDao.create(p1).getId(); Organization p2 = new Organization(); p2.getNameElement().setValue("testUpdateRejectsInvalidTypes"); try { - ourOrganizationDao.update(p2, new IdDt("Organization/" + p1id.getIdPart())); + p2.setId(new IdDt("Organization/" + p1id.getIdPart())); + ourOrganizationDao.update(p2); fail(); } catch (UnprocessableEntityException e) { // good } try { - ourOrganizationDao.update(p2, new IdDt("Patient/" + p1id.getIdPart())); + p2.setId(new IdDt("Patient/" + p1id.getIdPart())); + ourOrganizationDao.update(p2); fail(); } catch (UnprocessableEntityException e) { // good @@ -1589,7 +1730,7 @@ public class FhirResourceDaoTest { assertEquals("ABABA", p1id.getIdPart()); Patient p2 = new Patient(); - p2.addIdentifier().setSystem("urn:system").setValue( "testUpdateRejectsIdWhichPointsToForcedId02"); + p2.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsIdWhichPointsToForcedId02"); p2.addName().addFamily("Tester").addGiven("testUpdateRejectsIdWhichPointsToForcedId02"); IdDt p2id = ourPatientDao.create(p2).getId(); long p1longId = p2id.getIdPartAsLong() - 1; @@ -1602,7 +1743,8 @@ public class FhirResourceDaoTest { } try { - ourPatientDao.update(p1, new IdDt("Patient/" + p1longId)); + p1.setId(new IdDt("Patient/" + p1longId)); + ourPatientDao.update(p1); fail(); } catch (ResourceNotFoundException e) { // good @@ -1613,13 +1755,14 @@ public class FhirResourceDaoTest { @Test public void testReadForcedIdVersionHistory() throws InterruptedException { Patient p1 = new Patient(); - p1.addIdentifier().setSystem("urn:system").setValue( "testReadVorcedIdVersionHistory01"); + p1.addIdentifier().setSystem("urn:system").setValue("testReadVorcedIdVersionHistory01"); p1.setId("testReadVorcedIdVersionHistory"); IdDt p1id = ourPatientDao.create(p1).getId(); assertEquals("testReadVorcedIdVersionHistory", p1id.getIdPart()); - p1.addIdentifier().setSystem("urn:system").setValue( "testReadVorcedIdVersionHistory02"); - IdDt p1idv2 = ourPatientDao.update(p1, p1id).getId(); + p1.addIdentifier().setSystem("urn:system").setValue("testReadVorcedIdVersionHistory02"); + p1.setId(p1id); + IdDt p1idv2 = ourPatientDao.update(p1).getId(); assertEquals("testReadVorcedIdVersionHistory", p1idv2.getIdPart()); assertNotEquals(p1id.getValue(), p1idv2.getValue()); @@ -1652,7 +1795,7 @@ public class FhirResourceDaoTest { @SuppressWarnings("unchecked") @BeforeClass - public static void beforeClass() { + public static void beforeClass() { ourCtx = new ClassPathXmlApplicationContext("hapi-fhir-server-resourceproviders-dstu2.xml", "fhir-jpabase-spring-test-config.xml"); ourPatientDao = ourCtx.getBean("myPatientDaoDstu2", IFhirResourceDao.class); ourObservationDao = ourCtx.getBean("myObservationDaoDstu2", IFhirResourceDao.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java new file mode 100644 index 00000000000..f50971f412f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java @@ -0,0 +1,414 @@ +package ca.uhn.fhir.jpa.dao; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.model.dstu.composite.IdentifierDt; +import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.dstu.resource.Location; +import ca.uhn.fhir.model.dstu.resource.Observation; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class FhirSystemDaoDstu1Test { + + private static ClassPathXmlApplicationContext ourCtx; + private static FhirContext ourFhirContext; + private static IFhirResourceDao ourLocationDao; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu1Test.class); + private static IFhirResourceDao ourObservationDao; + private static IFhirResourceDao ourPatientDao; + private static IFhirSystemDao> ourSystemDao; + + @Test + public void testGetResourceCounts() { + Observation obs = new Observation(); + obs.getName().addCoding().setSystem("urn:system").setCode("testGetResourceCountsO01"); + ourObservationDao.create(obs); + + Map oldCounts = ourSystemDao.getResourceCounts(); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testGetResourceCountsP01"); + patient.addName().addFamily("Tester").addGiven("Joe"); + ourPatientDao.create(patient); + + Map newCounts = ourSystemDao.getResourceCounts(); + + if (oldCounts.containsKey("Patient")) { + assertEquals(oldCounts.get("Patient") + 1, (long) newCounts.get("Patient")); + } else { + assertEquals(1L, (long) newCounts.get("Patient")); + } + + assertEquals((long) oldCounts.get("Observation"), (long) newCounts.get("Observation")); + + } + + @Test + public void testHistory() throws Exception { + Date start = new Date(); + Thread.sleep(10); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testHistory"); + patient.addName().addFamily("Tester").addGiven("Joe"); + IdDt pid = ourPatientDao.create(patient).getId().toVersionless(); + + Thread.sleep(10); + patient.setId(pid); + IdDt newpid = ourPatientDao.update(patient).getId(); + + Thread.sleep(10); + patient.setId(pid); + IdDt newpid2 = ourPatientDao.update(patient).getId(); + + Thread.sleep(10); + patient.setId(pid); + IdDt newpid3 = ourPatientDao.update(patient).getId(); + + IBundleProvider values = ourSystemDao.history(start); + assertEquals(4, values.size()); + + List res = values.getResources(0, 4); + assertEquals(newpid3, res.get(0).getId()); + assertEquals(newpid2, res.get(1).getId()); + assertEquals(newpid, res.get(2).getId()); + assertEquals(pid.toUnqualifiedVersionless(), res.get(3).getId().toUnqualifiedVersionless()); + + Location loc = new Location(); + loc.getAddress().addLine("AAA"); + IdDt lid = ourLocationDao.create(loc).getId(); + + Location loc2 = new Location(); + loc2.getAddress().addLine("AAA"); + ourLocationDao.create(loc2).getId(); + + Thread.sleep(2000); + + values = ourLocationDao.history(start); + assertEquals(2, values.size()); + + values = ourLocationDao.history(lid.getIdPartAsLong(), start); + assertEquals(1, values.size()); + + } + + @Test + public void testPersistWithSimpleLink() { + Patient patient = new Patient(); + patient.setId(new IdDt("Patient/testPersistWithSimpleLinkP01")); + patient.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP01"); + patient.addName().addFamily("Tester").addGiven("Joe"); + + Observation obs = new Observation(); + obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01"); + obs.setSubject(new ResourceReferenceDt("Patient/testPersistWithSimpleLinkP01")); + + ourSystemDao.transaction(Arrays.asList((IResource) patient, obs)); + + String patientId = (patient.getId().getIdPart()); + String obsId = (obs.getId().getIdPart()); + + // assertThat(patientId, greaterThan(0L)); + // assertEquals(patientVersion, 1L); + // assertThat(obsId, greaterThan(patientId)); + // assertEquals(obsVersion, 1L); + + // Try to search + + IBundleProvider obsResults = ourObservationDao.search(Observation.SP_NAME, new IdentifierDt("urn:system", "testPersistWithSimpleLinkO01")); + assertEquals(1, obsResults.size()); + + IBundleProvider patResults = ourPatientDao.search(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testPersistWithSimpleLinkP01")); + assertEquals(1, obsResults.size()); + + IdDt foundPatientId = patResults.getResources(0, 1).get(0).getId(); + ResourceReferenceDt subject = obs.getSubject(); + assertEquals(foundPatientId.getIdPart(), subject.getReference().getIdPart()); + + // Update + + patient = (Patient) patResults.getResources(0, 1).get(0); + obs = (Observation) obsResults.getResources(0, 1).get(0); + patient.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02"); + obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO02"); + + ourSystemDao.transaction(Arrays.asList((IResource) patient, obs)); + + String patientId2 = (patient.getId().getIdPart()); + String patientVersion2 = (patient.getId().getVersionIdPart()); + String obsId2 = (obs.getId().getIdPart()); + String obsVersion2 = (obs.getId().getVersionIdPart()); + + assertEquals(patientId, patientId2); + assertEquals(patientVersion2, "2"); + assertEquals(obsId, obsId2); + assertEquals(obsVersion2, "2"); + + } + + @Test + public void testPersistWithUnknownId() { + Observation obs = new Observation(); + obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01"); + obs.setSubject(new ResourceReferenceDt("Patient/999998888888")); + + try { + ourSystemDao.transaction(Arrays.asList((IResource) obs)); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Resource Patient/999998888888 not found, specified in path: Observation.subject")); + } + + obs = new Observation(); + obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01"); + obs.setSubject(new ResourceReferenceDt("Patient/1.2.3.4")); + + try { + ourSystemDao.transaction(Arrays.asList((IResource) obs)); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Resource Patient/1.2.3.4 not found, specified in path: Observation.subject")); + } + + } + + @Test + public void testTagOperationss() throws Exception { + + TagList preSystemTl = ourSystemDao.getAllTags(); + + TagList tl1 = new TagList(); + tl1.addTag("testGetAllTagsScheme1", "testGetAllTagsTerm1", "testGetAllTagsLabel1"); + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("foo").setValue("testGetAllTags01"); + ResourceMetadataKeyEnum.TAG_LIST.put(p1, tl1); + ourPatientDao.create(p1); + + TagList tl2 = new TagList(); + tl2.addTag("testGetAllTagsScheme2", "testGetAllTagsTerm2", "testGetAllTagsLabel2"); + Observation o1 = new Observation(); + o1.getName().setText("testGetAllTags02"); + ResourceMetadataKeyEnum.TAG_LIST.put(o1, tl2); + IdDt o1id = ourObservationDao.create(o1).getId(); + assertTrue(o1id.getVersionIdPart() != null); + + TagList postSystemTl = ourSystemDao.getAllTags(); + assertEquals(preSystemTl.size() + 2, postSystemTl.size()); + assertEquals("testGetAllTagsLabel1", postSystemTl.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1").getLabel()); + + TagList tags = ourPatientDao.getAllResourceTags(); + assertEquals("testGetAllTagsLabel1", tags.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1").getLabel()); + assertNull(tags.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); + + TagList tags2 = ourObservationDao.getTags(o1id); + assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); + assertEquals("testGetAllTagsLabel2", tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2").getLabel()); + + o1.getResourceMetadata().remove(ResourceMetadataKeyEnum.TAG_LIST); + o1.setId(o1id); + IdDt o1id2 = ourObservationDao.update(o1).getId(); + assertTrue(o1id2.getVersionIdPart() != null); + + tags2 = ourObservationDao.getTags(o1id); + assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); + assertEquals("testGetAllTagsLabel2", tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2").getLabel()); + + tags2 = ourObservationDao.getTags(o1id2); + assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); + assertNotNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); + + /* + * Remove a tag from a version + */ + + ourObservationDao.removeTag(o1id2, "testGetAllTagsScheme2", "testGetAllTagsTerm2"); + tags2 = ourObservationDao.getTags(o1id2); + assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); + assertNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); + + tags2 = ourObservationDao.getTags(o1id); + assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); + assertNotNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); + + /* + * Add a tag + */ + ourObservationDao.addTag(o1id2, "testGetAllTagsScheme3", "testGetAllTagsTerm3", "testGetAllTagsLabel3"); + tags2 = ourObservationDao.getTags(o1id2); + assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); + assertNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); + assertNotNull(tags2.getTag("testGetAllTagsScheme3", "testGetAllTagsTerm3")); + assertEquals("testGetAllTagsLabel3", tags2.getTag("testGetAllTagsScheme3", "testGetAllTagsTerm3").getLabel()); + + tags2 = ourObservationDao.getTags(o1id); + assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); + assertNotNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); + + } + + + @Test(expected = InvalidRequestException.class) + public void testTransactionFailsWithDuplicateIds() { + Patient patient1 = new Patient(); + patient1.setId(new IdDt("Patient/testTransactionFailsWithDusplicateIds")); + patient1.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP01"); + + Patient patient2 = new Patient(); + patient2.setId(new IdDt("Patient/testTransactionFailsWithDusplicateIds")); + patient2.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02"); + + ourSystemDao.transaction(Arrays.asList((IResource) patient1, patient2)); + } + + @Test + public void testTransactionFromBundle() throws Exception { + + InputStream bundleRes = FhirSystemDaoDstu1Test.class.getResourceAsStream("/bundle-dstu1.xml"); + Bundle bundle = ourFhirContext.newXmlParser().parseBundle(new InputStreamReader(bundleRes)); + List res = bundle.toListOfResources(); + + ourSystemDao.transaction(res); + + Patient p1 = (Patient) res.get(0); + String id = p1.getId().getValue(); + ourLog.info("ID: {}", id); + assertThat(id, not(equalToIgnoringCase("74635"))); + assertThat(id, not(equalToIgnoringCase(""))); + } + + + /** + * Issue #55 + */ + @Test + public void testTransactionWithCidIds() throws Exception { + List res = new ArrayList(); + + Patient p1 = new Patient(); + p1.setId("cid:patient1"); + p1.addIdentifier().setSystem("system").setValue("testTransactionWithCidIds01"); + res.add(p1); + + Observation o1 = new Observation(); + o1.setId("cid:observation1"); + o1.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds02"); + o1.setSubject(new ResourceReferenceDt("Patient/cid:patient1")); + res.add(o1); + + Observation o2 = new Observation(); + o2.setId("cid:observation2"); + o2.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds03"); + o2.setSubject(new ResourceReferenceDt("Patient/cid:patient1")); + res.add(o2); + + ourSystemDao.transaction(res); + + assertTrue(p1.getId().getValue(), p1.getId().getIdPart().matches("^[0-9]+$")); + assertTrue(o1.getId().getValue(), o1.getId().getIdPart().matches("^[0-9]+$")); + assertTrue(o2.getId().getValue(), o2.getId().getIdPart().matches("^[0-9]+$")); + + assertThat(o1.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); + assertThat(o2.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); + + } + + @Test + public void testTransactionWithDelete() throws Exception { + + /* + * Create 3 + */ + + List res; + res = new ArrayList(); + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); + res.add(p1); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); + res.add(p2); + + Patient p3 = new Patient(); + p3.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); + res.add(p3); + + ourSystemDao.transaction(res); + + /* + * Verify + */ + + IBundleProvider results = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testTransactionWithDelete")); + assertEquals(3, results.size()); + + /* + * Now delete 2 + */ + + res = new ArrayList(); + List existing = results.getResources(0, 3); + + p1 = new Patient(); + p1.setId(existing.get(0).getId()); + ResourceMetadataKeyEnum.DELETED_AT.put(p1, InstantDt.withCurrentTime()); + res.add(p1); + + p2 = new Patient(); + p2.setId(existing.get(1).getId()); + ResourceMetadataKeyEnum.DELETED_AT.put(p2, InstantDt.withCurrentTime()); + res.add(p2); + + ourSystemDao.transaction(res); + + /* + * Verify + */ + + IBundleProvider results2 = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testTransactionWithDelete")); + assertEquals(1, results2.size()); + List existing2 = results2.getResources(0, 1); + assertEquals(existing2.get(0).getId(), existing.get(2).getId()); + + } + + @AfterClass + public static void afterClass() { + ourCtx.close(); + } + + @SuppressWarnings("unchecked") + @BeforeClass + public static void beforeClass() { + ourCtx = new ClassPathXmlApplicationContext("hapi-fhir-server-resourceproviders-dstu1.xml", "fhir-jpabase-spring-test-config.xml"); + ourFhirContext = ourCtx.getBean(FhirContext.class); + ourPatientDao = ourCtx.getBean("myPatientDaoDstu1", IFhirResourceDao.class); + ourObservationDao = ourCtx.getBean("myObservationDaoDstu1", IFhirResourceDao.class); + ourLocationDao = ourCtx.getBean("myLocationDaoDstu1", IFhirResourceDao.class); + ourSystemDao = ourCtx.getBean("mySystemDaoDstu1", IFhirSystemDao.class); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java new file mode 100644 index 00000000000..691a99c877a --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java @@ -0,0 +1,681 @@ +package ca.uhn.fhir.jpa.dao; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.hamcrest.Matchers; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; +import ca.uhn.fhir.model.dstu2.composite.QuantityDt; +import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.dstu2.resource.Location; +import ca.uhn.fhir.model.dstu2.resource.Observation; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; +import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; +import ca.uhn.fhir.model.valueset.BundleEntryTransactionOperationEnum; +import ca.uhn.fhir.rest.param.TokenParam; +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; + +public class FhirSystemDaoDstu2Test { + + private static ClassPathXmlApplicationContext ourCtx; + private static FhirContext ourFhirContext; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2Test.class); + private static IFhirResourceDao ourPatientDao; + private static IFhirSystemDao ourSystemDao; + private static IFhirResourceDao ourObservationDao; + + @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); + IdDt id = ourPatientDao.create(p).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).getTransaction().setMethod(HTTPVerbEnum.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getName().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getTransaction().setMethod(HTTPVerbEnum.POST); + + Bundle resp = ourSystemDao.transaction(request); + assertEquals(3, resp.getEntry().size()); + + Entry respEntry = resp.getEntry().get(1); + assertEquals(Constants.STATUS_HTTP_200_OK + "", respEntry.getTransactionResponse().getStatus()); + assertThat(respEntry.getTransactionResponse().getLocation(), endsWith("Patient/" + id.getIdPart() + "/_history/1")); + assertEquals("1", respEntry.getTransactionResponse().getEtag().get(0).getValue()); + + respEntry = resp.getEntry().get(2); + assertEquals(Constants.STATUS_HTTP_201_CREATED + "", respEntry.getTransactionResponse().getStatus()); + assertThat(respEntry.getTransactionResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getTransactionResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getTransactionResponse().getEtag().get(0).getValue()); + + o = (Observation) ourObservationDao.read(new IdDt(respEntry.getTransactionResponse().getLocationElement())); + assertEquals(id.toVersionless(), o.getSubject().getReference()); + assertEquals("1", o.getId().getVersionIdPart()); + + } + + + @Test + public void testTransactionReadAndSearch() { + String methodName = "testTransactionReadAndSearch"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IdDt idv1 = ourPatientDao.create(p).getId(); + ourLog.info("Created patient, got id: {}", idv1); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addFamily("Family Name"); + p.setId("Patient/" + methodName); + IdDt idv2 = ourPatientDao.update(p).getId(); + ourLog.info("Updated patient, got id: {}", idv2); + + Bundle request = new Bundle(); + request.addEntry().getTransaction().setMethod(HTTPVerbEnum.GET).setUrl(idv1.toUnqualifiedVersionless().getValue()); + request.addEntry().getTransaction().setMethod(HTTPVerbEnum.GET).setUrl(idv1.toUnqualified().getValue()); + request.addEntry().getTransaction().setMethod(HTTPVerbEnum.GET).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Bundle resp = ourSystemDao.transaction(request); + + assertEquals(4, resp.getEntry().size()); + + Entry nextEntry; + + nextEntry = resp.getEntry().get(1); + assertEquals(Patient.class, nextEntry.getResource().getClass()); + assertEquals(idv2.toUnqualified(), nextEntry.getResource().getId().toUnqualified()); + + nextEntry = resp.getEntry().get(2); + assertEquals(Patient.class, nextEntry.getResource().getClass()); + assertEquals(idv1.toUnqualified(), nextEntry.getResource().getId().toUnqualified()); + + nextEntry = resp.getEntry().get(3); + assertEquals(Bundle.class, nextEntry.getResource().getClass()); + + Bundle respBundle = (Bundle)nextEntry.getResource(); + assertEquals(1, respBundle.getTotal().intValue()); + } + + + + @Test + public void testTransactionCreateMatchUrlWithTwoMatch() { + String methodName = "testTransactionCreateMatchUrlWithTwoMatch"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IdDt id = ourPatientDao.create(p).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + id = ourPatientDao.create(p).getId(); + ourLog.info("Created patient, got it: {}", id); + + Bundle request = new Bundle(); + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addFamily("Hello"); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getTransaction().setMethod(HTTPVerbEnum.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getName().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getTransaction().setMethod(HTTPVerbEnum.POST); + + try { + ourSystemDao.transaction(request); + fail(); + } catch (PreconditionFailedException e) { + assertThat(e.getMessage(), containsString("with match URL \"Patient")); + } + } + + @Test + public void testTransactionCreateMatchUrlWithZeroMatch() { + String methodName = "testTransactionCreateMatchUrlWithZeroMatch"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addFamily("Hello"); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getTransaction().setMethod(HTTPVerbEnum.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getName().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getTransaction().setMethod(HTTPVerbEnum.POST); + + Bundle resp = ourSystemDao.transaction(request); + assertEquals(3, resp.getEntry().size()); + + Entry respEntry = resp.getEntry().get(1); + assertEquals(Constants.STATUS_HTTP_201_CREATED + "", respEntry.getTransactionResponse().getStatus()); + String patientId = respEntry.getTransactionResponse().getLocation(); + assertThat(patientId, not(endsWith("Patient/" + methodName + "/_history/1"))); + assertThat(patientId, (endsWith("/_history/1"))); + assertThat(patientId, (containsString("Patient/"))); + assertEquals("1", respEntry.getTransactionResponse().getEtag().get(0).getValue()); + + respEntry = resp.getEntry().get(2); + assertEquals(Constants.STATUS_HTTP_201_CREATED + "", respEntry.getTransactionResponse().getStatus()); + assertThat(respEntry.getTransactionResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getTransactionResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getTransactionResponse().getEtag().get(0).getValue()); + + o = (Observation) ourObservationDao.read(new IdDt(respEntry.getTransactionResponse().getLocationElement())); + assertEquals(new IdDt(patientId).toUnqualifiedVersionless(), o.getSubject().getReference()); + } + + @Test + public void testTransactionCreateNoMatchUrl() { + String methodName = "testTransactionCreateNoMatchUrl"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + request.addEntry().setResource(p).getTransaction().setMethod(HTTPVerbEnum.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName); + + Bundle resp = ourSystemDao.transaction(request); + assertEquals(2, resp.getEntry().size()); + + Entry respEntry = resp.getEntry().get(1); + assertEquals(Constants.STATUS_HTTP_201_CREATED + "", respEntry.getTransactionResponse().getStatus()); + String patientId = respEntry.getTransactionResponse().getLocation(); + assertThat(patientId, not(containsString("test"))); + } + + @Test + public void testTransactionDeleteMatchUrlWithOneMatch() { + String methodName = "testTransactionDeleteMatchUrlWithOneMatch"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IdDt id = ourPatientDao.create(p).getId(); + ourLog.info("Created patient, got it: {}", id); + + Bundle request = new Bundle(); + request.addEntry().getTransaction().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Bundle resp = ourSystemDao.transaction(request); + assertEquals(2, resp.getEntry().size()); + + Entry nextEntry = resp.getEntry().get(1); + assertEquals(Constants.STATUS_HTTP_204_NO_CONTENT + "", nextEntry.getTransactionResponse().getStatus()); + + try { + ourPatientDao.read(id.toVersionless()); + fail(); + } catch (ResourceGoneException e) { + // ok + } + + try { + ourPatientDao.read(new IdDt("Patient/" + methodName)); + fail(); + } catch (ResourceNotFoundException e) { + // ok + } + + IBundleProvider history = ourPatientDao.history(id, null); + assertEquals(2, history.size()); + + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get(history.getResources(0, 0).get(0))); + assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get(history.getResources(0, 0).get(0)).getValue()); + assertNull(ResourceMetadataKeyEnum.DELETED_AT.get(history.getResources(1, 1).get(0))); + + } + + @Test + public void testTransactionDeleteMatchUrlWithTwoMatch() { + String methodName = "testTransactionDeleteMatchUrlWithTwoMatch"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IdDt id = ourPatientDao.create(p).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + id = ourPatientDao.create(p).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); + + Bundle request = new Bundle(); + request.addEntry().getTransaction().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + try { + ourSystemDao.transaction(request); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), containsString("resource with match URL \"Patient?")); + } + } + + @Test + public void testTransactionDeleteByResourceId() { + String methodName = "testTransactionDeleteByResourceId"; + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue(methodName); + IdDt id1 = ourPatientDao.create(p1).getId(); + ourLog.info("Created patient, got it: {}", id1); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue(methodName); + p2.setId("Patient/" + methodName); + IdDt id2 = ourPatientDao.create(p2).getId(); + ourLog.info("Created patient, got it: {}", id2); + + Bundle request = new Bundle(); + + request.addEntry().getTransaction().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient/" + id1.getIdPart()); + request.addEntry().getTransaction().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient/" + id2.getIdPart()); + + ourPatientDao.read(id1.toVersionless()); + ourPatientDao.read(id2.toVersionless()); + + Bundle resp = ourSystemDao.transaction(request); + + assertEquals(3, resp.getEntry().size()); + assertEquals("204", resp.getEntry().get(1).getTransactionResponse().getStatus()); + assertEquals("204", resp.getEntry().get(2).getTransactionResponse().getStatus()); + + try { + ourPatientDao.read(id1.toVersionless()); + fail(); + } catch (ResourceGoneException e) { + // good + } + + try { + ourPatientDao.read(id2.toVersionless()); + fail(); + } catch (ResourceGoneException e) { + // good + } + + } + + @Test + public void testTransactionDeleteMatchUrlWithZeroMatch() { + String methodName = "testTransactionDeleteMatchUrlWithZeroMatch"; + + Bundle request = new Bundle(); + request.addEntry().getTransaction().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + try { + ourSystemDao.transaction(request); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), containsString("resource matching URL \"Patient?")); + } + } + + @Test + public void testTransactionDeleteNoMatchUrl() { + String methodName = "testTransactionDeleteNoMatchUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IdDt id = ourPatientDao.create(p).getId(); + ourLog.info("Created patient, got it: {}", id); + + Bundle request = new Bundle(); + request.addEntry().getTransaction().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Bundle res = ourSystemDao.transaction(request); + assertEquals(2, res.getEntry().size()); + + assertEquals(Constants.STATUS_HTTP_204_NO_CONTENT + "", res.getEntry().get(1).getTransactionResponse().getStatus()); + + try { + ourPatientDao.read(id.toVersionless()); + fail(); + } catch (ResourceGoneException e) { + // ok + } + } + + @Test(expected = InvalidRequestException.class) + public void testTransactionFailsWithDuplicateIds() { + Bundle request = new Bundle(); + + Patient patient1 = new Patient(); + patient1.setId(new IdDt("Patient/testTransactionFailsWithDusplicateIds")); + patient1.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP01"); + request.addEntry().setResource(patient1).getTransaction().setMethod(HTTPVerbEnum.POST); + + Patient patient2 = new Patient(); + patient2.setId(new IdDt("Patient/testTransactionFailsWithDusplicateIds")); + patient2.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02"); + request.addEntry().setResource(patient2).getTransaction().setMethod(HTTPVerbEnum.POST); + + ourSystemDao.transaction(request); + } + + @Test + public void testTransactionUpdateMatchUrlWithOneMatch() { + String methodName = "testTransactionUpdateMatchUrlWithOneMatch"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IdDt id = ourPatientDao.create(p).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).getTransaction().setMethod(HTTPVerbEnum.PUT).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getName().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getTransaction().setMethod(HTTPVerbEnum.POST); + + Bundle resp = ourSystemDao.transaction(request); + assertEquals(3, resp.getEntry().size()); + + Entry nextEntry = resp.getEntry().get(1); + assertEquals("200", nextEntry.getTransactionResponse().getStatus()); + assertThat(nextEntry.getTransactionResponse().getLocation(), not(containsString("test"))); + assertEquals(id.toVersionless(), p.getId().toVersionless()); + assertNotEquals(id, p.getId()); + assertThat(p.getId().toString(), endsWith("/_history/2")); + + nextEntry = resp.getEntry().get(1); + assertEquals(""+Constants.STATUS_HTTP_200_OK, nextEntry.getTransactionResponse().getStatus()); + assertThat(nextEntry.getTransactionResponse().getLocation(), not(emptyString())); + + nextEntry = resp.getEntry().get(2); + o = ourObservationDao.read(new IdDt(nextEntry.getTransactionResponse().getLocation())); + assertEquals(id.toVersionless(), o.getSubject().getReference()); + + } + + @Test + public void testTransactionUpdateMatchUrlWithTwoMatch() { + String methodName = "testTransactionUpdateMatchUrlWithTwoMatch"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IdDt id = ourPatientDao.create(p).getId(); + ourLog.info("Created patient, got it: {}", id); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + id = ourPatientDao.create(p).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).getTransaction().setMethod(HTTPVerbEnum.PUT).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getName().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getTransaction().setMethod(HTTPVerbEnum.POST); + + try { + ourSystemDao.transaction(request); + fail(); + } catch (PreconditionFailedException e) { + assertThat(e.getMessage(), containsString("with match URL \"Patient")); + } + } + + @Test + public void testTransactionUpdateMatchUrlWithZeroMatch() { + String methodName = "testTransactionUpdateMatchUrlWithZeroMatch"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addName().addFamily("Hello"); + IdDt id = ourPatientDao.create(p).getId(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().addFamily("Hello"); + p.setId(id); + request.addEntry().setResource(p).getTransaction().setMethod(HTTPVerbEnum.PUT).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName); + + Observation o = new Observation(); + o.getName().setText("Some Observation"); + o.getSubject().setReference(id); + request.addEntry().setResource(o).getTransaction().setMethod(HTTPVerbEnum.POST); + + Bundle resp = ourSystemDao.transaction(request); + assertEquals(3, resp.getEntry().size()); + + Entry nextEntry = resp.getEntry().get(1); + assertEquals(Constants.STATUS_HTTP_201_CREATED + "", nextEntry.getTransactionResponse().getStatus()); + + assertThat(nextEntry.getTransactionResponse().getLocation(), not(containsString("test"))); + assertNotEquals(id.toVersionless(), new IdDt(nextEntry.getTransactionResponse().getLocation()).toVersionless()); + + assertThat(nextEntry.getTransactionResponse().getLocation(), endsWith("/_history/1")); + + nextEntry = resp.getEntry().get(1); + assertEquals(""+Constants.STATUS_HTTP_201_CREATED, nextEntry.getTransactionResponse().getStatus()); + + nextEntry = resp.getEntry().get(2); + o = ourObservationDao.read(new IdDt(nextEntry.getTransactionResponse().getLocation())); + assertEquals(id.toVersionless(), o.getSubject().getReference()); + + } + + + @Test + public void testTransactionUpdateNoMatchUrl() { + String methodName = "testTransactionUpdateNoMatchUrl"; + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.setId("Patient/" + methodName); + IdDt id = ourPatientDao.create(p).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).getTransaction().setMethod(HTTPVerbEnum.PUT).setUrl("Patient/"+ id.getIdPart()); + + Observation o = new Observation(); + o.getName().setText("Some Observation"); + o.getSubject().setReference("Patient/" + methodName); + request.addEntry().setResource(o).getTransaction().setMethod(HTTPVerbEnum.POST); + + Bundle resp = ourSystemDao.transaction(request); + assertEquals(3, resp.getEntry().size()); + + Entry nextEntry = resp.getEntry().get(1); + assertEquals("200", nextEntry.getTransactionResponse().getStatus()); + + assertThat(nextEntry.getTransactionResponse().getLocation(), (containsString("test"))); + assertEquals(id.toVersionless(), new IdDt(nextEntry.getTransactionResponse().getLocation()).toVersionless()); + assertNotEquals(id, new IdDt(nextEntry.getTransactionResponse().getLocation())); + assertThat(nextEntry.getTransactionResponse().getLocation(), endsWith("/_history/2")); + + nextEntry = resp.getEntry().get(2); + assertEquals(""+Constants.STATUS_HTTP_201_CREATED, nextEntry.getTransactionResponse().getStatus()); + + o = ourObservationDao.read(new IdDt(resp.getEntry().get(2).getTransactionResponse().getLocation())); + assertEquals(id.toVersionless(), o.getSubject().getReference()); + + } + + // + // + // /** + // * Issue #55 + // */ + // @Test + // public void testTransactionWithCidIds() throws Exception { + // Bundle request = new Bundle(); + // + // Patient p1 = new Patient(); + // p1.setId("cid:patient1"); + // p1.addIdentifier().setSystem("system").setValue("testTransactionWithCidIds01"); + // res.add(p1); + // + // Observation o1 = new Observation(); + // o1.setId("cid:observation1"); + // o1.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds02"); + // o1.setSubject(new ResourceReferenceDt("Patient/cid:patient1")); + // res.add(o1); + // + // Observation o2 = new Observation(); + // o2.setId("cid:observation2"); + // o2.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds03"); + // o2.setSubject(new ResourceReferenceDt("Patient/cid:patient1")); + // res.add(o2); + // + // ourSystemDao.transaction(res); + // + // assertTrue(p1.getId().getValue(), p1.getId().getIdPart().matches("^[0-9]+$")); + // assertTrue(o1.getId().getValue(), o1.getId().getIdPart().matches("^[0-9]+$")); + // assertTrue(o2.getId().getValue(), o2.getId().getIdPart().matches("^[0-9]+$")); + // + // assertThat(o1.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); + // assertThat(o2.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); + // + // } + // + // @Test + // public void testTransactionWithDelete() throws Exception { + // Bundle request = new Bundle(); + // + // /* + // * Create 3 + // */ + // + // List res; + // res = new ArrayList(); + // + // Patient p1 = new Patient(); + // p1.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); + // res.add(p1); + // + // Patient p2 = new Patient(); + // p2.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); + // res.add(p2); + // + // Patient p3 = new Patient(); + // p3.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); + // res.add(p3); + // + // ourSystemDao.transaction(res); + // + // /* + // * Verify + // */ + // + // IBundleProvider results = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", + // "testTransactionWithDelete")); + // assertEquals(3, results.size()); + // + // /* + // * Now delete 2 + // */ + // + // request = new Bundle(); + // res = new ArrayList(); + // List existing = results.getResources(0, 3); + // + // p1 = new Patient(); + // p1.setId(existing.get(0).getId()); + // ResourceMetadataKeyEnum.DELETED_AT.put(p1, InstantDt.withCurrentTime()); + // res.add(p1); + // + // p2 = new Patient(); + // p2.setId(existing.get(1).getId()); + // ResourceMetadataKeyEnum.DELETED_AT.put(p2, InstantDt.withCurrentTime()); + // res.add(p2); + // + // ourSystemDao.transaction(res); + // + // /* + // * Verify + // */ + // + // IBundleProvider results2 = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", + // "testTransactionWithDelete")); + // assertEquals(1, results2.size()); + // List existing2 = results2.getResources(0, 1); + // assertEquals(existing2.get(0).getId(), existing.get(2).getId()); + // + // } + + @AfterClass + public static void afterClass() { + ourCtx.close(); + } + + @SuppressWarnings("unchecked") + @BeforeClass + public static void beforeClass() { + ourCtx = new ClassPathXmlApplicationContext("hapi-fhir-server-resourceproviders-dstu2.xml", "fhir-jpabase-spring-test-config.xml"); + ourFhirContext = ourCtx.getBean(FhirContext.class); + assertEquals(FhirVersionEnum.DSTU2, ourFhirContext.getVersion().getVersion()); + ourPatientDao = ourCtx.getBean("myPatientDaoDstu2", IFhirResourceDao.class); + ourObservationDao = ourCtx.getBean("myObservationDaoDstu2", IFhirResourceDao.class); + ourSystemDao = ourCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoTest.java deleted file mode 100644 index e5d1f1d16e6..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoTest.java +++ /dev/null @@ -1,899 +0,0 @@ -package ca.uhn.fhir.jpa.dao; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.model.api.TagList; -import ca.uhn.fhir.model.dstu.composite.IdentifierDt; -import ca.uhn.fhir.model.dstu.composite.QuantityDt; -import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; -import ca.uhn.fhir.model.dstu.resource.Location; -import ca.uhn.fhir.model.dstu.resource.Observation; -import ca.uhn.fhir.model.dstu.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.model.valueset.BundleEntryTransactionOperationEnum; -import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; - -public class FhirSystemDaoTest { - - private static ClassPathXmlApplicationContext ourCtx; - private static FhirContext ourFhirContext; - private static IFhirResourceDao ourLocationDao; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoTest.class); - private static IFhirResourceDao ourObservationDao; - private static IFhirResourceDao ourPatientDao; - private static IFhirSystemDao> ourSystemDao; - - @Test - public void testGetResourceCounts() { - Observation obs = new Observation(); - obs.getName().addCoding().setSystem("urn:system").setCode("testGetResourceCountsO01"); - ourObservationDao.create(obs); - - Map oldCounts = ourSystemDao.getResourceCounts(); - - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("testGetResourceCountsP01"); - patient.addName().addFamily("Tester").addGiven("Joe"); - ourPatientDao.create(patient); - - Map newCounts = ourSystemDao.getResourceCounts(); - - if (oldCounts.containsKey("Patient")) { - assertEquals(oldCounts.get("Patient") + 1, (long) newCounts.get("Patient")); - } else { - assertEquals(1L, (long) newCounts.get("Patient")); - } - - assertEquals((long) oldCounts.get("Observation"), (long) newCounts.get("Observation")); - - } - - @Test - public void testHistory() throws Exception { - Date start = new Date(); - Thread.sleep(10); - - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("testHistory"); - patient.addName().addFamily("Tester").addGiven("Joe"); - IdDt pid = ourPatientDao.create(patient).getId().toVersionless(); - - Thread.sleep(10); - IdDt newpid = ourPatientDao.update(patient, pid).getId(); - - Thread.sleep(10); - IdDt newpid2 = ourPatientDao.update(patient, pid).getId(); - - Thread.sleep(10); - IdDt newpid3 = ourPatientDao.update(patient, pid).getId(); - - IBundleProvider values = ourSystemDao.history(start); - assertEquals(4, values.size()); - - List res = values.getResources(0, 4); - assertEquals(newpid3, res.get(0).getId()); - assertEquals(newpid2, res.get(1).getId()); - assertEquals(newpid, res.get(2).getId()); - assertEquals(pid.toUnqualifiedVersionless(), res.get(3).getId().toUnqualifiedVersionless()); - - Location loc = new Location(); - loc.getAddress().addLine("AAA"); - IdDt lid = ourLocationDao.create(loc).getId(); - - Location loc2 = new Location(); - loc2.getAddress().addLine("AAA"); - ourLocationDao.create(loc2).getId(); - - Thread.sleep(2000); - - values = ourLocationDao.history(start); - assertEquals(2, values.size()); - - values = ourLocationDao.history(lid.getIdPartAsLong(), start); - assertEquals(1, values.size()); - - } - - @Test - public void testPersistWithSimpleLink() { - Patient patient = new Patient(); - patient.setId(new IdDt("Patient/testPersistWithSimpleLinkP01")); - patient.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP01"); - patient.addName().addFamily("Tester").addGiven("Joe"); - - Observation obs = new Observation(); - obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01"); - obs.setSubject(new ResourceReferenceDt("Patient/testPersistWithSimpleLinkP01")); - - ourSystemDao.transaction(Arrays.asList((IResource) patient, obs)); - - String patientId = (patient.getId().getIdPart()); - String obsId = (obs.getId().getIdPart()); - - // assertThat(patientId, greaterThan(0L)); - // assertEquals(patientVersion, 1L); - // assertThat(obsId, greaterThan(patientId)); - // assertEquals(obsVersion, 1L); - - // Try to search - - IBundleProvider obsResults = ourObservationDao.search(Observation.SP_NAME, new IdentifierDt("urn:system", "testPersistWithSimpleLinkO01")); - assertEquals(1, obsResults.size()); - - IBundleProvider patResults = ourPatientDao.search(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testPersistWithSimpleLinkP01")); - assertEquals(1, obsResults.size()); - - IdDt foundPatientId = patResults.getResources(0, 1).get(0).getId(); - ResourceReferenceDt subject = obs.getSubject(); - assertEquals(foundPatientId.getIdPart(), subject.getReference().getIdPart()); - - // Update - - patient = (Patient) patResults.getResources(0, 1).get(0); - obs = (Observation) obsResults.getResources(0, 1).get(0); - patient.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02"); - obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO02"); - - ourSystemDao.transaction(Arrays.asList((IResource) patient, obs)); - - String patientId2 = (patient.getId().getIdPart()); - String patientVersion2 = (patient.getId().getVersionIdPart()); - String obsId2 = (obs.getId().getIdPart()); - String obsVersion2 = (obs.getId().getVersionIdPart()); - - assertEquals(patientId, patientId2); - assertEquals(patientVersion2, "2"); - assertEquals(obsId, obsId2); - assertEquals(obsVersion2, "2"); - - } - - @Test - public void testPersistWithUnknownId() { - Observation obs = new Observation(); - obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01"); - obs.setSubject(new ResourceReferenceDt("Patient/999998888888")); - - try { - ourSystemDao.transaction(Arrays.asList((IResource) obs)); - } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("Resource Patient/999998888888 not found, specified in path: Observation.subject")); - } - - obs = new Observation(); - obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01"); - obs.setSubject(new ResourceReferenceDt("Patient/1.2.3.4")); - - try { - ourSystemDao.transaction(Arrays.asList((IResource) obs)); - } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("Resource Patient/1.2.3.4 not found, specified in path: Observation.subject")); - } - - } - - @Test - public void testTagOperationss() throws Exception { - - TagList preSystemTl = ourSystemDao.getAllTags(); - - TagList tl1 = new TagList(); - tl1.addTag("testGetAllTagsScheme1", "testGetAllTagsTerm1", "testGetAllTagsLabel1"); - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("foo").setValue("testGetAllTags01"); - ResourceMetadataKeyEnum.TAG_LIST.put(p1, tl1); - ourPatientDao.create(p1); - - TagList tl2 = new TagList(); - tl2.addTag("testGetAllTagsScheme2", "testGetAllTagsTerm2", "testGetAllTagsLabel2"); - Observation o1 = new Observation(); - o1.getName().setText("testGetAllTags02"); - ResourceMetadataKeyEnum.TAG_LIST.put(o1, tl2); - IdDt o1id = ourObservationDao.create(o1).getId(); - assertTrue(o1id.getVersionIdPart() != null); - - TagList postSystemTl = ourSystemDao.getAllTags(); - assertEquals(preSystemTl.size() + 2, postSystemTl.size()); - assertEquals("testGetAllTagsLabel1", postSystemTl.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1").getLabel()); - - TagList tags = ourPatientDao.getAllResourceTags(); - assertEquals("testGetAllTagsLabel1", tags.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1").getLabel()); - assertNull(tags.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); - - TagList tags2 = ourObservationDao.getTags(o1id); - assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); - assertEquals("testGetAllTagsLabel2", tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2").getLabel()); - - o1.getResourceMetadata().remove(ResourceMetadataKeyEnum.TAG_LIST); - IdDt o1id2 = ourObservationDao.update(o1, o1id).getId(); - assertTrue(o1id2.getVersionIdPart() != null); - - tags2 = ourObservationDao.getTags(o1id); - assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); - assertEquals("testGetAllTagsLabel2", tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2").getLabel()); - - tags2 = ourObservationDao.getTags(o1id2); - assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); - assertNotNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); - - /* - * Remove a tag from a version - */ - - ourObservationDao.removeTag(o1id2, "testGetAllTagsScheme2", "testGetAllTagsTerm2"); - tags2 = ourObservationDao.getTags(o1id2); - assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); - assertNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); - - tags2 = ourObservationDao.getTags(o1id); - assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); - assertNotNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); - - /* - * Add a tag - */ - ourObservationDao.addTag(o1id2, "testGetAllTagsScheme3", "testGetAllTagsTerm3", "testGetAllTagsLabel3"); - tags2 = ourObservationDao.getTags(o1id2); - assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); - assertNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); - assertNotNull(tags2.getTag("testGetAllTagsScheme3", "testGetAllTagsTerm3")); - assertEquals("testGetAllTagsLabel3", tags2.getTag("testGetAllTagsScheme3", "testGetAllTagsTerm3").getLabel()); - - tags2 = ourObservationDao.getTags(o1id); - assertNull(tags2.getTag("testGetAllTagsScheme1", "testGetAllTagsTerm1")); - assertNotNull(tags2.getTag("testGetAllTagsScheme2", "testGetAllTagsTerm2")); - - } - - @Test - public void testTransactionCreateMatchUrlWithOneMatch() { - String methodName = "testTransactionCreateMatchUrlWithOneMatch"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.setId("Patient/" + methodName); - IdDt id = ourPatientDao.create(p).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); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.CREATE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - Observation o = new Observation(); - o.getName().setText("Some Observation"); - o.getSubject().setReference("Patient/" + methodName); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p, o)); - assertEquals(3, resp.size()); - - p = (Patient) resp.get(1); - assertEquals(BundleEntryTransactionOperationEnum.NOOP, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertEquals(id, p.getId()); - - o = (Observation) resp.get(2); - assertEquals(BundleEntryTransactionOperationEnum.CREATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(o)); - assertEquals(id.toVersionless(), o.getSubject().getReference()); - - } - - @Test - public void testTransactionCreateMatchUrlWithTwoMatch() { - String methodName = "testTransactionCreateMatchUrlWithTwoMatch"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - IdDt id = ourPatientDao.create(p).getId(); - ourLog.info("Created patient, got it: {}", id); - - p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - id = ourPatientDao.create(p).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); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.CREATE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - Observation o = new Observation(); - o.getName().setText("Some Observation"); - o.getSubject().setReference("Patient/" + methodName); - - try { - ourSystemDao.transaction(Arrays.asList((IResource) p, o)); - fail(); - } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("with match URL \"Patient")); - } - } - - @Test - public void testTransactionCreateMatchUrlWithZeroMatch() { - String methodName = "testTransactionCreateMatchUrlWithZeroMatch"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.addName().addFamily("Hello"); - p.setId("Patient/" + methodName); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.CREATE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - Observation o = new Observation(); - o.getName().setText("Some Observation"); - o.getSubject().setReference("Patient/" + methodName); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p, o)); - assertEquals(3, resp.size()); - - p = (Patient) resp.get(1); - assertEquals(BundleEntryTransactionOperationEnum.CREATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertThat(p.getId().getIdPart(), not(containsString("test"))); - - o = (Observation) resp.get(2); - assertEquals(BundleEntryTransactionOperationEnum.CREATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(o)); - assertEquals(p.getId().toVersionless(), o.getSubject().getReference()); - - } - - @Test - public void testTransactionCreateNoMatchUrl() { - String methodName = "testTransactionCreateNoMatchUrl"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.setId("Patient/" + methodName); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.CREATE); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p)); - assertEquals(2, resp.size()); - p = (Patient) resp.get(1); - - assertEquals(BundleEntryTransactionOperationEnum.CREATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertThat(p.getId().getIdPart(), not(containsString("test"))); - } - - @Test - public void testTransactionDeleteMatchUrlWithOneMatch() { - String methodName = "testTransactionDeleteMatchUrlWithOneMatch"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - IdDt id = ourPatientDao.create(p).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); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.DELETE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p)); - assertEquals(2, resp.size()); - - p = (Patient) resp.get(1); - assertEquals(BundleEntryTransactionOperationEnum.DELETE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertThat(p.getId().toVersionless().toString(), not(containsString("test"))); - assertEquals(id.toVersionless(), p.getId().toVersionless()); - assertNotEquals(id, p.getId()); - assertThat(p.getId().toString(), endsWith("/_history/2")); - - try { - ourPatientDao.read(id.toVersionless()); - fail(); - } catch (ResourceGoneException e) { - // ok - } - - try { - ourPatientDao.read(new IdDt("Patient/" + methodName)); - fail(); - } catch (ResourceNotFoundException e) { - // ok - } - - } - - @Test - public void testTransactionDeleteMatchUrlWithTwoMatch() { - String methodName = "testTransactionDeleteMatchUrlWithTwoMatch"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - IdDt id = ourPatientDao.create(p).getId(); - ourLog.info("Created patient, got it: {}", id); - - p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - id = ourPatientDao.create(p).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); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.DELETE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - Observation o = new Observation(); - o.getName().setText("Some Observation"); - o.getSubject().setReference("Patient/" + methodName); - - try { - ourSystemDao.transaction(Arrays.asList((IResource) p, o)); - fail(); - } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("with match URL \"Patient")); - } - } - - @Test - public void testTransactionDeleteMatchUrlWithZeroMatch() { - String methodName = "testTransactionDeleteMatchUrlWithZeroMatch"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName + "ZZZ"); - p.addName().addFamily("Hello"); - IdDt id = ourPatientDao.create(p).getId(); - - p = new Patient(); - p.addName().addFamily("Hello"); - p.setId("Patient/" + methodName); - IdDt id2 = ourPatientDao.create(p).getId(); - - p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.addName().addFamily("Hello"); - p.setId("Patient/" + methodName); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.DELETE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p)); - assertEquals(2, resp.size()); - - p = (Patient) resp.get(1); - assertEquals(BundleEntryTransactionOperationEnum.DELETE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertThat(p.getId().toVersionless().toString(), (containsString("test"))); - assertThat(p.getId().toString(), endsWith("/_history/2")); - assertEquals(id2.toVersionless(), p.getId().toVersionless()); - assertNotEquals(id2, p.getId()); - - try { - ourPatientDao.read(id2.toVersionless()); - fail(); - } catch (ResourceGoneException e) { - // ok - } - - Patient found = ourPatientDao.read(id); - assertEquals(id, found.getId()); - - } - - @Test - public void testTransactionDeleteNoMatchUrl() { - String methodName = "testTransactionDeleteNoMatchUrl"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.setId("Patient/" + methodName); - IdDt id = ourPatientDao.create(p).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); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.DELETE); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p)); - assertEquals(2, resp.size()); - - p = (Patient) resp.get(1); - assertEquals(BundleEntryTransactionOperationEnum.DELETE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get(p).getValue()); - - try { - ourPatientDao.read(id.toVersionless()); - fail(); - } catch (ResourceGoneException e) { - // ok - } - } - - @Test(expected = InvalidRequestException.class) - public void testTransactionFailsWithDuplicateIds() { - Patient patient1 = new Patient(); - patient1.setId(new IdDt("Patient/testTransactionFailsWithDusplicateIds")); - patient1.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP01"); - - Patient patient2 = new Patient(); - patient2.setId(new IdDt("Patient/testTransactionFailsWithDusplicateIds")); - patient2.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02"); - - ourSystemDao.transaction(Arrays.asList((IResource) patient1, patient2)); - } - -// @Test TODO: re-enable - public void testTransactionFromBundle() throws Exception { - - InputStream bundleRes = FhirSystemDaoTest.class.getResourceAsStream("/bundle.json"); - Bundle bundle = ourFhirContext.newJsonParser().parseBundle(new InputStreamReader(bundleRes)); - List res = bundle.toListOfResources(); - - ourSystemDao.transaction(res); - - Patient p1 = (Patient) res.get(0); - String id = p1.getId().getValue(); - ourLog.info("ID: {}", id); - assertThat(id, not(containsString("5556918"))); - assertThat(id, not(equalToIgnoringCase(""))); - } - - @Test - public void testTransactionUpdateMatchUrlWithOneMatch() { - String methodName = "testTransactionUpdateMatchUrlWithOneMatch"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - IdDt id = ourPatientDao.create(p).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); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.UPDATE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - Observation o = new Observation(); - o.getName().setText("Some Observation"); - o.getSubject().setReference("Patient/" + methodName); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p, o)); - assertEquals(3, resp.size()); - - p = (Patient) resp.get(1); - assertEquals(BundleEntryTransactionOperationEnum.UPDATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertThat(p.getId().toVersionless().toString(), not(containsString("test"))); - assertEquals(id.toVersionless(), p.getId().toVersionless()); - assertNotEquals(id, p.getId()); - assertThat(p.getId().toString(), endsWith("/_history/2")); - - o = (Observation) resp.get(2); - assertEquals(BundleEntryTransactionOperationEnum.CREATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(o)); - assertEquals(id.toVersionless(), o.getSubject().getReference()); - - } - - @Test - public void testTransactionUpdateMatchUrlWithTwoMatch() { - String methodName = "testTransactionUpdateMatchUrlWithTwoMatch"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - IdDt id = ourPatientDao.create(p).getId(); - ourLog.info("Created patient, got it: {}", id); - - p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - id = ourPatientDao.create(p).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); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.UPDATE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - Observation o = new Observation(); - o.getName().setText("Some Observation"); - o.getSubject().setReference("Patient/" + methodName); - - try { - ourSystemDao.transaction(Arrays.asList((IResource) p, o)); - fail(); - } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("with match URL \"Patient")); - } - } - - @Test - public void testTransactionUpdateMatchUrlWithZeroMatch() { - String methodName = "testTransactionUpdateMatchUrlWithZeroMatch"; - - Patient p = new Patient(); - p.addName().addFamily("Hello"); - IdDt id = ourPatientDao.create(p).getId(); - - p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.addName().addFamily("Hello"); - p.setId(id); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.UPDATE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - Observation o = new Observation(); - o.getName().setText("Some Observation"); - o.getSubject().setReference(id); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p, o)); - assertEquals(3, resp.size()); - - p = (Patient) resp.get(1); - assertEquals(BundleEntryTransactionOperationEnum.UPDATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertEquals(id.toVersionless(), p.getId().toVersionless()); - assertNotEquals(id, p.getId()); - assertThat(p.getId().toString(), endsWith("/_history/2")); - - o = (Observation) resp.get(2); - assertEquals(BundleEntryTransactionOperationEnum.CREATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(o)); - assertEquals(p.getId().toVersionless(), o.getSubject().getReference()); - - } - - @Test - public void testTransactionUpdateMatchUrlWithZeroMatchAndNotPreExisting() { - String methodName = "testTransactionUpdateMatchUrlWithZeroMatchAndNotPreExisting"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.addName().addFamily("Hello"); - p.setId("Patient/" + methodName); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.UPDATE); - ResourceMetadataKeyEnum.LINK_SEARCH.put(p, "Patient?identifier=urn%3Asystem%7C" + methodName); - - Observation o = new Observation(); - o.getName().setText("Some Observation"); - o.getSubject().setReference("Patient/" + methodName); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p, o)); - assertEquals(3, resp.size()); - - p = (Patient) resp.get(1); - assertEquals(BundleEntryTransactionOperationEnum.CREATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertThat(p.getId().toVersionless().toString(), containsString("test")); - assertThat(p.getId().toString(), endsWith("/_history/1")); - - o = (Observation) resp.get(2); - assertEquals(BundleEntryTransactionOperationEnum.CREATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(o)); - assertEquals(p.getId().toVersionless(), o.getSubject().getReference()); - - } - - @Test - public void testTransactionUpdateNoMatchUrl() { - String methodName = "testTransactionUpdateNoMatchUrl"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.setId("Patient/" + methodName); - IdDt id = ourPatientDao.create(p).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); - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.put(p, BundleEntryTransactionOperationEnum.UPDATE); - - Observation o = new Observation(); - o.getName().setText("Some Observation"); - o.getSubject().setReference("Patient/" + methodName); - - List resp = ourSystemDao.transaction(Arrays.asList((IResource) p, o)); - assertEquals(3, resp.size()); - - p = (Patient) resp.get(1); - assertEquals(BundleEntryTransactionOperationEnum.UPDATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(p)); - assertThat(p.getId().toVersionless().toString(), containsString("test")); - assertEquals(id.toVersionless(), p.getId().toVersionless()); - assertNotEquals(id, p.getId()); - assertThat(p.getId().toString(), endsWith("/_history/2")); - - o = (Observation) resp.get(2); - assertEquals(BundleEntryTransactionOperationEnum.CREATE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_OPERATION.get(o)); - assertEquals(id.toVersionless(), o.getSubject().getReference()); - - } - - @Test - public void testTransactionUpdateNoOperationSpecified() throws Exception { - List res = new ArrayList(); - - Patient p1 = new Patient(); - p1.getId().setValue("testTransactionWithUpdateXXX01"); - p1.addIdentifier().setSystem("system").setValue("testTransactionWithUpdate01"); - res.add(p1); - - Observation p2 = new Observation(); - p2.getId().setValue("testTransactionWithUpdateXXX02"); - p2.getIdentifier().setSystem("system").setValue("testTransactionWithUpdate02"); - p2.setSubject(new ResourceReferenceDt("Patient/testTransactionWithUpdateXXX01")); - res.add(p2); - - ourSystemDao.transaction(res); - - assertFalse(p1.getId().isEmpty()); - assertFalse(p2.getId().isEmpty()); - assertEquals("testTransactionWithUpdateXXX01", p1.getId().getIdPart()); - assertEquals("testTransactionWithUpdateXXX02", p2.getId().getIdPart()); - assertNotEquals("testTransactionWithUpdateXXX01", p1.getId().getVersionIdPart()); - assertNotEquals("testTransactionWithUpdateXXX02", p2.getId().getVersionIdPart()); - assertEquals(p1.getId().toUnqualified().toVersionless(), p2.getSubject().getReference()); - - IdDt p1id = p1.getId().toUnqualified().toVersionless(); - IdDt p1idWithVer = p1.getId().toUnqualified(); - IdDt p2id = p2.getId().toUnqualified().toVersionless(); - IdDt p2idWithVer = p2.getId().toUnqualified(); - - /* - * Make some changes - */ - - res = new ArrayList(); - - p1 = new Patient(); - p1.getId().setValue("testTransactionWithUpdateXXX01"); - p1.addIdentifier().setSystem("system").setValue("testTransactionWithUpdate01"); - p1.addName().addFamily("Name1"); - res.add(p1); - - p2 = new Observation(); - p2.getId().setValue("testTransactionWithUpdateXXX02"); - p2.getIdentifier().setSystem("system").setValue("testTransactionWithUpdate02"); - p2.setSubject(new ResourceReferenceDt("Patient/testTransactionWithUpdateXXX01")); - p2.addReferenceRange().setHigh(new QuantityDt(123L)); - res.add(p2); - - List results = ourSystemDao.transaction(res); - - assertEquals(p1id, results.get(1).getId().toUnqualified().toVersionless()); - assertEquals(p2id, results.get(2).getId().toUnqualified().toVersionless()); - assertNotEquals(p1idWithVer, results.get(1).getId().toUnqualified()); - assertNotEquals(p2idWithVer, results.get(2).getId().toUnqualified()); - - } - - /** - * Issue #55 - */ - @Test - public void testTransactionWithCidIds() throws Exception { - List res = new ArrayList(); - - Patient p1 = new Patient(); - p1.setId("cid:patient1"); - p1.addIdentifier().setSystem("system").setValue("testTransactionWithCidIds01"); - res.add(p1); - - Observation o1 = new Observation(); - o1.setId("cid:observation1"); - o1.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds02"); - o1.setSubject(new ResourceReferenceDt("Patient/cid:patient1")); - res.add(o1); - - Observation o2 = new Observation(); - o2.setId("cid:observation2"); - o2.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds03"); - o2.setSubject(new ResourceReferenceDt("Patient/cid:patient1")); - res.add(o2); - - ourSystemDao.transaction(res); - - assertTrue(p1.getId().getValue(), p1.getId().getIdPart().matches("^[0-9]+$")); - assertTrue(o1.getId().getValue(), o1.getId().getIdPart().matches("^[0-9]+$")); - assertTrue(o2.getId().getValue(), o2.getId().getIdPart().matches("^[0-9]+$")); - - assertThat(o1.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); - assertThat(o2.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); - - } - - @Test - public void testTransactionWithDelete() throws Exception { - - /* - * Create 3 - */ - - List res; - res = new ArrayList(); - - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); - res.add(p1); - - Patient p2 = new Patient(); - p2.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); - res.add(p2); - - Patient p3 = new Patient(); - p3.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); - res.add(p3); - - ourSystemDao.transaction(res); - - /* - * Verify - */ - - IBundleProvider results = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testTransactionWithDelete")); - assertEquals(3, results.size()); - - /* - * Now delete 2 - */ - - res = new ArrayList(); - List existing = results.getResources(0, 3); - - p1 = new Patient(); - p1.setId(existing.get(0).getId()); - ResourceMetadataKeyEnum.DELETED_AT.put(p1, InstantDt.withCurrentTime()); - res.add(p1); - - p2 = new Patient(); - p2.setId(existing.get(1).getId()); - ResourceMetadataKeyEnum.DELETED_AT.put(p2, InstantDt.withCurrentTime()); - res.add(p2); - - ourSystemDao.transaction(res); - - /* - * Verify - */ - - IBundleProvider results2 = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testTransactionWithDelete")); - assertEquals(1, results2.size()); - List existing2 = results2.getResources(0, 1); - assertEquals(existing2.get(0).getId(), existing.get(2).getId()); - - } - - @AfterClass - public static void afterClass() { - ourCtx.close(); - } - - @SuppressWarnings("unchecked") - @BeforeClass - public static void beforeClass() { - ourCtx = new ClassPathXmlApplicationContext("hapi-fhir-server-resourceproviders-dstu1.xml", "fhir-jpabase-spring-test-config.xml"); - ourFhirContext = ourCtx.getBean(FhirContext.class); - ourPatientDao = ourCtx.getBean("myPatientDaoDstu1", IFhirResourceDao.class); - ourObservationDao = ourCtx.getBean("myObservationDaoDstu1", IFhirResourceDao.class); - ourLocationDao = ourCtx.getBean("myLocationDaoDstu1", IFhirResourceDao.class); - ourSystemDao = ourCtx.getBean("mySystemDaoDstu1", IFhirSystemDao.class); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java index b47b5ab02d8..9dce7dfa397 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java @@ -19,7 +19,6 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; diff --git a/hapi-fhir-jpaserver-base/src/test/resources/bundle-dstu1.xml b/hapi-fhir-jpaserver-base/src/test/resources/bundle-dstu1.xml new file mode 100644 index 00000000000..c8c8106e0c4 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/bundle-dstu1.xml @@ -0,0 +1,158 @@ + + + Search results for resource type Patient + urn:uuid:d05b8832-2ec2-4df1-9a8f-905ba57823 + + + + + + 2015-02-21T17:08:03Z + 303 + + Patient "74635" Version "2" + http://fhir.healthintersections.com.au/open/Patient/74635 + + 2015-01-29T13:54:03Z + + Anonymous (24.130.25.156) + + 2015-02-21T17:08:03Z + + + + +
+

+ Generated Narrative +

+

+ identifier: ??

+

+ name: Eve Everywoman

+

+ telecom: ph: 555-555-2003(work)

+

+ gender: Female + (Details : {http://hl7.org/fhir/v3/AdministrativeGender code "F" := "Female", given as "Female"}) +

+

+ birthDate: 1955-01-06

+

+ address: 2222 Home Street, (home)

+

+ photo:

+

+ active: -1

+
+
+ + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+
+ +
Generated Narrative identifier: ?? name: Eve Everywoman telecom: ph: 555-555-2003(work) gender: Female (Details : {http://hl7.org/fhir/v3/AdministrativeGender code "F" := "Female", given as "Female"}) birthDate: 1955-01-06 address: 2222 Home Street…
+
+
+ + Patient "8483548" Version "1" + http://fhir.healthintersections.com.au/open/Patient/8483548 + + + 2015-01-29T02:19:50Z + + Anonymous (service) + + 2015-02-21T17:08:03Z + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
NamePeter James + Chalmers("Jim")
Address534 Erewhon, Pleasantville, Orange County, 3999
ContactsHome: unknown. Work: (03) 5555 6473
IdMRN: 12345 (Acme Healthcare)
+
+ + + + + + + +
+ + + + + + + + +
+ +
+
+ +
Name Peter James Chalmers ("Jim") Address 534 Erewhon, Pleasantville, Orange County, 3999 Contacts Home: unknown. Work: (03) 5555 6473 …
+
+
+
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDev.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDev.java deleted file mode 100644 index 5055e5ae381..00000000000 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDev.java +++ /dev/null @@ -1,74 +0,0 @@ -package ca.uhn.fhir.rest.client; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.StringReader; -import java.nio.charset.Charset; - -import org.apache.commons.io.input.ReaderInputStream; -import org.apache.http.HttpResponse; -import org.apache.http.ProtocolVersion; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.message.BasicHeader; -import org.apache.http.message.BasicStatusLine; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.rest.server.Constants; - -public class GenericClientTestDev { - private static FhirContext ourCtx; - private HttpClient myHttpClient; - private HttpResponse myHttpResponse; - - - @BeforeClass - public static void beforeClass() { - ourCtx = FhirContext.forDstu2(); - } - - - @Before - public void before() { - myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); - ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); - ourCtx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.NEVER); - myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); - } - - - @Test - public void testSearchByString() throws Exception { - String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().value("james")) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Patient?name=james", capt.getValue().getURI().toString()); - assertEquals(Patient.class, response.getEntries().get(0).getResource().getClass()); - - } - - -} diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDstu2.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDstu2.java new file mode 100644 index 00000000000..7851bbdb3f9 --- /dev/null +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDstu2.java @@ -0,0 +1,212 @@ +package ca.uhn.fhir.rest.client; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.Charset; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicStatusLine; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; + +public class GenericClientTestDstu2 { + private static FhirContext ourCtx; + private HttpClient myHttpClient; + private HttpResponse myHttpResponse; + + + @BeforeClass + public static void beforeClass() { + ourCtx = FhirContext.forDstu2(); + } + + + @Before + public void before() { + myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); + ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); + ourCtx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.NEVER); + myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + } + + + @Test + public void testSearchByString() throws Exception { + String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("james")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?name=james", capt.getValue().getURI().toString()); + assertEquals(Patient.class, response.getEntries().get(0).getResource().getClass()); + + } + + @Test + public void testDeleteConditional() throws Exception { + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_204_NO_CONTENT, "")); +// when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int idx = 0; + + client.delete().resourceById(new IdDt("Patient/123")).execute(); + assertEquals("DELETE", capt.getAllValues().get(idx).getMethod()); + assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + client.delete().resourceConditionalByUrl("Patient?name=foo").execute(); + assertEquals("DELETE", capt.getAllValues().get(idx).getMethod()); + assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + client.delete().resourceConditionalByType("Patient").where(Patient.NAME.matches().value("foo")).execute(); + assertEquals("DELETE", capt.getAllValues().get(idx).getMethod()); + assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + } + + @Test + public void testCreateConditional() throws Exception { + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_204_NO_CONTENT, "")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int idx = 0; + + Patient p = new Patient(); + p.addName().addFamily("FOOFAMILY"); + + client.create().resource(p).conditionalByUrl("Patient?name=foo").execute(); + assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("")); + assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).getURI().toString()); + assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_IF_NONE_EXIST).getValue()); + assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); + idx++; + + client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).execute(); + assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("")); + assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).getURI().toString()); + assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_IF_NONE_EXIST).getValue()); + assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); + idx++; + + } + + + @Test + public void testUpdateConditional() throws Exception { + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_204_NO_CONTENT, "")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int idx = 0; + + Patient p = new Patient(); + p.addName().addFamily("FOOFAMILY"); + + client.update().resource(p).conditionalByUrl("Patient?name=foo").execute(); + assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("")); + assertEquals("PUT", capt.getAllValues().get(idx).getRequestLine().getMethod()); + assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").execute(); + assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("")); + assertEquals("PUT", capt.getAllValues().get(idx).getRequestLine().getMethod()); + assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + client.update().resource(p).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("")); + assertEquals("PUT", capt.getAllValues().get(idx).getRequestLine().getMethod()); + assertEquals("http://example.com/fhir/Patient?name=foo&address=AAA%5C%7CBBB", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("")); + assertEquals("PUT", capt.getAllValues().get(idx).getRequestLine().getMethod()); + assertEquals("http://example.com/fhir/Patient?name=foo&address=AAA%5C%7CBBB", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + } + + private String extractBody(ArgumentCaptor capt, int count) throws IOException { + String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(count)).getEntity().getContent(), "UTF-8"); + return body; + } + +} diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/UpdateConditionalTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/UpdateConditionalTest.java new file mode 100644 index 00000000000..bdf667faa0f --- /dev/null +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/UpdateConditionalTest.java @@ -0,0 +1,133 @@ +package ca.uhn.fhir.rest.server; + +import static org.junit.Assert.*; + +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.model.dstu2.resource.DiagnosticOrder; +import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport; +import ca.uhn.fhir.model.dstu2.resource.Observation; +import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; +import ca.uhn.fhir.model.dstu2.resource.Organization; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.util.PortUtil; + +/** + * Created by dsotnikov on 2/25/2014. + */ +public class UpdateConditionalTest { + private static CloseableHttpClient ourClient; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UpdateConditionalTest.class); + private static int ourPort; + private static Server ourServer; + + @Test + public void testUpdate() throws Exception { + + Patient patient = new Patient(); + patient.addIdentifier().setValue("002"); + + HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/001"); + httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + + HttpResponse status = ourClient.execute(httpPost); + + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + + ourLog.info("Response was:\n{}", responseContent); + + OperationOutcome oo = new FhirContext().newXmlParser().parseResource(OperationOutcome.class, responseContent); + assertEquals("OODETAILS", oo.getIssueFirstRep().getDetails()); + + assertEquals(200, status.getStatusLine().getStatusCode()); + assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue()); + assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("content-location").getValue()); + + } + + + + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + ourPort = PortUtil.findFreePort(); + ourServer = new Server(ourPort); + + PatientProvider patientProvider = new PatientProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + RestfulServer servlet = new RestfulServer(); + servlet.setResourceProviders(patientProvider); + ServletHolder servletHolder = new ServletHolder(servlet); + proxyHandler.addServletWithMapping(servletHolder, "/*"); + ourServer.setHandler(proxyHandler); + ourServer.start(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourClient = builder.build(); + + } + + private static String ourLastConditionalUrl; + + + @Before + public void before() { + ourLastConditionalUrl=null; + } + + public static class PatientProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + + @Update() + public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) { + IdDt id = theId.withVersion(thePatient.getIdentifierFirstRep().getValue()); + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().setDetails("OODETAILS"); + if (theId.getValueAsString().contains("CREATE")) { + return new MethodOutcome(id,oo, true); + } + + return new MethodOutcome(id,oo); + } + + } + +} From c03d6293334fcb61e1f9b5fb472f61070d7d3281 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 23 Feb 2015 10:01:39 -0500 Subject: [PATCH 2/2] Fix compile failure --- .../src/main/java/ca/uhn/fhir/model/dev/FhirDev.java | 2 -- .../src/main/java/ca/uhn/fhir/model/dev/FhirDev.java | 6 ------ .../src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java | 7 +------ .../src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java | 6 +----- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java index 3c94a4bf79d..579434ad2da 100644 --- a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java @@ -23,12 +23,10 @@ package ca.uhn.fhir.model.dev; import java.io.InputStream; import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.instance.model.api.IBaseExtension; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.base.composite.BaseContainedDt; diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java index 4fc2a43ac68..f8448224d30 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java @@ -108,11 +108,5 @@ public class FhirDev implements IFhirVersion { } - @Override - public IBaseExtension newExtension() { - return null; - } - - } diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java index d3b0383ada0..db4b397d8dd 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java @@ -380,11 +380,6 @@ public class FhirDstu1 implements IFhirVersion { return ContainedDt.class; } - @Override - public IBaseExtension newExtension() { - return null; - } - - + } diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java index 54fc0759cb3..f46b860e98f 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java @@ -93,9 +93,5 @@ public class FhirDstu2 implements IFhirVersion { throw new UnsupportedOperationException(); } - @Override - public IBaseExtension newExtension() { - return null; - } - + }