implements IDao {
}
/**
- * This method is called when an update to an existing resource detects that the resource supplied for update is
- * missing a tag/profile/security label that the currently persisted resource holds.
+ * This method is called when an update to an existing resource detects that the resource supplied for update is missing a tag/profile/security label that the currently persisted resource holds.
*
- * The default implementation removes any profile declarations, but leaves tags and security labels in place.
- * Subclasses may choose to override and change this behaviour.
+ * The default implementation removes any profile declarations, but leaves tags and security labels in place. Subclasses may choose to override and change this behaviour.
*
*
* @param theEntity
@@ -695,8 +694,7 @@ public abstract class BaseHapiFhirDao implements IDao {
* @param theTag
* The tag
* @return Retturns true
if the tag should be removed
- * @see Updates to Tags, Profiles, and Security
- * Labels for a description of the logic that the default behaviour folows.
+ * @see Updates to Tags, Profiles, and Security Labels for a description of the logic that the default behaviour folows.
*/
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
if (theTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
@@ -709,6 +707,10 @@ public abstract class BaseHapiFhirDao implements IDao {
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
SearchParameterMap paramMap = translateMatchUrl(theMatchUrl, resourceDef);
+
+ if (paramMap.isEmpty()) {
+ throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
+ }
IFhirResourceDao dao = getDao(theResourceType);
Set ids = dao.searchForIdsWithAndOr(paramMap);
@@ -719,23 +721,28 @@ public abstract class BaseHapiFhirDao implements IDao {
public static SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) {
SearchParameterMap paramMap = new SearchParameterMap();
List parameters;
- try {
- String matchUrl = theMatchUrl;
- if (matchUrl.indexOf('?') == -1) {
- throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Error was: URL does not contain any parameters ('?' not detected)");
- }
- matchUrl = matchUrl.replace("|", "%7C");
- matchUrl = matchUrl.replace("=>=", "=%3E%3D");
- matchUrl = matchUrl.replace("=<=", "=%3C%3D");
- matchUrl = matchUrl.replace("=>", "=%3E");
- matchUrl = matchUrl.replace("=<", "=%3C");
- parameters = URLEncodedUtils.parse(new URI(matchUrl), "UTF-8");
- } catch (URISyntaxException e) {
- throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Error was: " + e.toString());
+ String matchUrl = theMatchUrl;
+ int questionMarkIndex = matchUrl.indexOf('?');
+ if (questionMarkIndex != -1) {
+ matchUrl = matchUrl.substring(questionMarkIndex + 1);
}
+ matchUrl = matchUrl.replace("|", "%7C");
+ matchUrl = matchUrl.replace("=>=", "=%3E%3D");
+ matchUrl = matchUrl.replace("=<=", "=%3C%3D");
+ matchUrl = matchUrl.replace("=>", "=%3E");
+ matchUrl = matchUrl.replace("=<", "=%3C");
+ if (matchUrl.contains(" ")) {
+ throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
+ }
+
+ parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
ArrayListMultimap nameToParamLists = ArrayListMultimap.create();
for (NameValuePair next : parameters) {
+ if (isBlank(next.getValue())) {
+ continue;
+ }
+
String paramName = next.getName();
String qualifier = null;
for (int i = 0; i < paramMap.size(); i++) {
@@ -779,11 +786,11 @@ public abstract class BaseHapiFhirDao implements IDao {
}
continue;
}
-
+
if (nextParamName.startsWith("_")) {
continue;
}
-
+
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);
@@ -1030,8 +1037,7 @@ public abstract class BaseHapiFhirDao implements IDao {
}
} else if (theForHistoryOperation) {
/*
- * If the create and update times match, this was when the resource was created
- * so we should mark it as a POST. Otherwise, it's a PUT.
+ * If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
*/
Date published = theEntity.getPublished().getValue();
Date updated = theEntity.getUpdated().getValue();
@@ -1041,7 +1047,7 @@ public abstract class BaseHapiFhirDao implements IDao {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
}
}
-
+
res.setId(theEntity.getIdDt());
ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
@@ -1125,8 +1131,9 @@ public abstract class BaseHapiFhirDao implements IDao {
}
@SuppressWarnings("unchecked")
- protected ResourceTable updateEntity(final IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime) {
-
+ protected ResourceTable updateEntity(final IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
+ boolean theUpdateVersion, Date theUpdateTime) {
+
/*
* This should be the very first thing..
*/
@@ -1134,14 +1141,15 @@ public abstract class BaseHapiFhirDao implements IDao {
validateResourceForStorage((T) theResource, theEntity);
String resourceType = myContext.getResourceDefinition(theResource).getName();
if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) {
- throw new UnprocessableEntityException("Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
+ throw new UnprocessableEntityException(
+ "Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
}
}
if (theEntity.getPublished() == null) {
theEntity.setPublished(theUpdateTime);
}
-
+
if (theUpdateHistory) {
final ResourceHistoryTable historyEntry = theEntity.toHistory();
myEntityManager.persist(historyEntry);
@@ -1238,7 +1246,7 @@ public abstract class BaseHapiFhirDao implements IDao {
theEntity.setResourceLinks(links);
theEntity.setHasLinks(links.isEmpty() == false);
theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
-
+
} else {
populateResourceIntoEntity(theResource, theEntity);
@@ -1261,7 +1269,7 @@ public abstract class BaseHapiFhirDao implements IDao {
} else {
theEntity = myEntityManager.merge(theEntity);
-
+
postUpdate(theEntity, (T) theResource);
}
@@ -1354,37 +1362,37 @@ public abstract class BaseHapiFhirDao implements IDao {
}
/**
- * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the
- * first time.
+ * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the first time.
*
* @param theEntity
* The resource
- * @param theResource The resource being persisted
+ * @param theResource
+ * The resource being persisted
*/
protected void postUpdate(ResourceTable theEntity, T theResource) {
// nothing
}
/**
- * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the
- * first time.
+ * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the first time.
*
* @param theEntity
* The resource
- * @param theResource The resource being persisted
+ * @param theResource
+ * The resource being persisted
*/
protected void postPersist(ResourceTable theEntity, T theResource) {
// nothing
}
/**
- * This method is invoked immediately before storing a new resource, or an update to an existing resource to allow
- * the DAO to ensure that it is valid for persistence. By default, checks for the "subsetted" tag and rejects
- * resources which have it. Subclasses should call the superclass implementation to preserve this check.
+ * This method is invoked immediately before storing a new resource, or an update to an existing resource to allow the DAO to ensure that it is valid for persistence. By default, checks for the
+ * "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
*
* @param theResource
* The resource that is about to be persisted
- * @param theEntityToSave TODO
+ * @param theEntityToSave
+ * TODO
*/
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
IResource res = (IResource) theResource;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
index 580a0a8ad74..873d6270991 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
@@ -56,6 +56,7 @@ import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
@@ -129,7 +130,8 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 toPurge = mySubscriptionTableDao.findInactiveBeforeCutoff(cutoff);
for (SubscriptionTable subscriptionTable : toPurge) {
final IdDt subscriptionId = subscriptionTable.getSubscriptionResource().getIdDt();
- ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}", new Object[] { subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll() });
+ ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}",
+ new Object[] { subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll() });
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(new TransactionCallback() {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java
index e94f476224f..b54426020a9 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java
@@ -1,19 +1,32 @@
package ca.uhn.fhir.jpa.dao;
+import java.io.IOException;
+import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
+import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
+import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.rest.server.IBundleProvider;
public class BaseJpaTest {
+ public static String loadClasspath(String resource) throws IOException {
+ InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream(resource);
+ if (bundleRes == null) {
+ throw new NullPointerException("Can not load " + resource);
+ }
+ String bundleStr = IOUtils.toString(bundleRes);
+ return bundleStr;
+ }
+
@AfterClass
public static void afterClassShutdownDerby() throws SQLException {
// try {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java
index eccde1dc555..38d2f69887e 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1Test.java
@@ -18,7 +18,6 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
-import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
@@ -28,7 +27,6 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
-import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource;
@@ -346,8 +344,8 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
@Test
public void testTransactionWithCidIds2() throws Exception {
- InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/dstu1_bundle.xml");
- String bundleStr = IOUtils.toString(bundleRes);
+ String resource = "/dstu1_bundle.xml";
+ String bundleStr = loadClasspath(resource);
Bundle bundle = ourFhirContext.newXmlParser().parseBundle(bundleStr);
List res = new ArrayList();
@@ -363,6 +361,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
assertThat(encodeResourceToString, not(containsString("smsp")));
}
+
/**
* This is the correct way to do this, not {@link #testTransactionWithCidIds()}
*/
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java
index 4ab80bb4565..2027d7037ba 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java
@@ -44,6 +44,7 @@ import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
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.EntryRequest;
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryResponse;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
@@ -354,6 +355,42 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
}
}
+ @Test
+ public void testTransactionCreateWithInvalidMatchUrl() {
+ String methodName = "testTransactionCreateWithInvalidMatchUrl";
+ Bundle request = new Bundle();
+
+ Patient p;
+ p = new Patient();
+ p.addIdentifier().setSystem("urn:system").setValue(methodName);
+ EntryRequest entry = request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST);
+
+ try {
+ entry.setIfNoneExist("Patient?identifier identifier" + methodName);
+ mySystemDao.transaction(request);
+ fail();
+ } catch (InvalidRequestException e) {
+ assertEquals("Failed to parse match URL[Patient?identifier identifiertestTransactionCreateWithInvalidMatchUrl] - URL is invalid (must not contain spaces)", e.getMessage());
+ }
+
+ try {
+ entry.setIfNoneExist("Patient?identifier=");
+ mySystemDao.transaction(request);
+ fail();
+ } catch (InvalidRequestException e) {
+ assertEquals("Invalid match URL[Patient?identifier=] - URL has no search parameters", e.getMessage());
+ }
+
+ try {
+ entry.setIfNoneExist("Patient?foo=bar");
+ mySystemDao.transaction(request);
+ fail();
+ } catch (InvalidRequestException e) {
+ assertEquals("Failed to parse match URL[Patient?foo=bar] - Resource type Patient does not have a parameter with name: foo", e.getMessage());
+ }
+ }
+
+
public void testTransactionCreateWithDuplicateMatchUrl02() {
String methodName = "testTransactionCreateWithDuplicateMatchUrl02";
Bundle request = new Bundle();
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 25a25cff4ce..2142bd27f9a 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
@@ -1619,6 +1619,20 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
+ @Test
+ public void testTransaction() throws Exception {
+ String contents = loadClasspath("/update.xml");
+ HttpPost post = new HttpPost(ourServerBase);
+ post.setEntity(new StringEntity(contents, ContentType.create("application/xml+fhir", "UTF-8")));
+ CloseableHttpResponse resp = ourHttpClient.execute(post);
+ try {
+ assertEquals(200, resp.getStatusLine().getStatusCode());
+ String output= IOUtils.toString(resp.getEntity().getContent());
+ ourLog.info(output);
+ } finally {
+ resp.close();
+ }
+ }
@Test
public void testUpdateRejectsInvalidTypes() throws InterruptedException {
diff --git a/hapi-fhir-jpaserver-base/src/test/resources/update.xml b/hapi-fhir-jpaserver-base/src/test/resources/update.xml
new file mode 100644
index 00000000000..d739fa8b378
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/resources/update.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ o
+
+
+
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/logback.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/logback.xml
index 652e8e884b3..8f569a59655 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/logback.xml
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/logback.xml
@@ -9,6 +9,24 @@
+
+
+ DEBUG
+
+ ${fhir.logdir}/subscription.log
+
+ ${fhir.logdir}/subscription.%i.log.zip
+ 1
+ 10
+
+
+ 5MB
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{req.remoteAddr}] [%X{req.userAgent}] %-5level %logger{36} %msg%n
+
+
+
DEBUG
@@ -39,19 +57,25 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
-
+
\ No newline at end of file
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 84547808dc1..7754e200f34 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -115,6 +115,11 @@
Correct issues with Android library. Thanks to
Thomas Andersen for the submission!
+
+ JPA server incorrectly rejected match URLs
+ if they did not contain a question mark. Thanks
+ to Bill de Beaubien for reporting!
+