();
for (BundleEntry next : resource.getEntries()) {
if (next.getResource() != null) {
retVal.add(next.getResource());
}
}
-
+
return retVal;
}
@Override
public void initializeTypes(Method theMethod, Class extends Collection>> theOuterCollectionType, Class extends Collection>> theInnerCollectionType, Class> theParameterType) {
if (theOuterCollectionType != null) {
- throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + TransactionParam.class.getName() + " but can not be a collection of collections");
+ throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is annotated with @" + TransactionParam.class.getName() + " but can not be a collection of collections");
}
- if (theInnerCollectionType.equals(List.class)==false) {
- throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + TransactionParam.class.getName() + " but is not of type List<" + IResource.class.getCanonicalName()+">");
- }
- if (theParameterType.equals(IResource.class)==false) {
- throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + TransactionParam.class.getName() + " but is not of type List<" + IResource.class.getCanonicalName()+">");
+ if (theParameterType.equals(Bundle.class)) {
+ myParamIsBundle=true;
+ if (theInnerCollectionType!=null) {
+ throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is annotated with @" + TransactionParam.class.getName() + " but is not of type List<" + IResource.class.getCanonicalName()
+ + "> or Bundle");
+ }
+ } else {
+ myParamIsBundle=false;
+ if (theInnerCollectionType.equals(List.class) == false) {
+ throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is annotated with @" + TransactionParam.class.getName() + " but is not of type List<" + IResource.class.getCanonicalName()
+ + "> or Bundle");
+ }
+ if (theParameterType.equals(IResource.class) == false) {
+ throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is annotated with @" + TransactionParam.class.getName() + " but is not of type List<" + IResource.class.getCanonicalName()
+ + "> or Bundle");
+ }
}
}
-
+
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java
index 7b3a1cfae2e..91b44f85f8a 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.param;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.server.Constants;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java
index 169a6b8e26e..3d3eee15854 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.util;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
public class UrlUtil {
public static boolean isAbsolute(String theValue) {
diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml
index bf59ef0fd16..5ee8c6e8eb5 100644
--- a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml
+++ b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml
@@ -1201,6 +1201,11 @@
+
+ Transaction methods require one parameter annotated with @TransactionParam, and that
+ parameter may be of type List<IResource> or Bundle.
+
+
Example URL to invoke this method:
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/TransactionClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/TransactionClientTest.java
index 6d9e877893c..857d5657880 100644
--- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/TransactionClientTest.java
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/TransactionClientTest.java
@@ -75,7 +75,7 @@ public class TransactionClientTest {
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_ATOM_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(createBundle()), Charset.forName("UTF-8")));
- client.searchWithParam(resources);
+ client.transaction(resources);
assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue();
@@ -92,6 +92,47 @@ public class TransactionClientTest {
assertTrue(bundle.getEntries().get(1).getId().isEmpty());
}
+
+
+ @Test
+ public void testSimpleTransactionWithBundleParam() throws Exception {
+ Patient patient = new Patient();
+ patient.setId(new IdDt("Patient/testPersistWithSimpleLinkP01"));
+ patient.addIdentifier("urn:system", "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"));
+
+ Bundle transactionBundle = Bundle.withResources(Arrays.asList((IResource)patient, obs), ctx, "http://foo");
+
+ IBundleClient client = ctx.newRestfulClient(IBundleClient.class, "http://foo");
+
+ ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class);
+ when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
+ when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
+ when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_ATOM_XML + "; charset=UTF-8"));
+ when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(createBundle()), Charset.forName("UTF-8")));
+
+ client.transaction(transactionBundle);
+
+ assertEquals(HttpPost.class, capt.getValue().getClass());
+ HttpPost post = (HttpPost) capt.getValue();
+ assertEquals("http://foo/", post.getURI().toString());
+
+ Bundle bundle = ctx.newXmlParser().parseBundle(new InputStreamReader(post.getEntity().getContent()));
+ ourLog.info(ctx.newXmlParser().setPrettyPrint(true).encodeBundleToString(bundle));
+
+ assertEquals(2, bundle.size());
+ assertEquals("http://foo/Patient/testPersistWithSimpleLinkP01", bundle.getEntries().get(0).getId().getValue());
+ assertEquals("http://foo/Patient/testPersistWithSimpleLinkP01", bundle.getEntries().get(0).getLinkSelf().getValue());
+ assertEquals(null, bundle.getEntries().get(0).getLinkAlternate().getValue());
+
+ assertTrue(bundle.getEntries().get(1).getId().isEmpty());
+
+ }
+
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TransactionClientTest.class);
private String createBundle() {
return ctx.newXmlParser().encodeBundleToString(new Bundle());
@@ -100,7 +141,15 @@ public class TransactionClientTest {
private interface IClient extends IBasicClient {
@Transaction
- public List searchWithParam(@TransactionParam List theResources);
+ public List transaction(@TransactionParam List theResources);
+
+
+ }
+
+ private interface IBundleClient extends IBasicClient {
+
+ @Transaction
+ public List transaction(@TransactionParam Bundle theResources);
}
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java
index af688330df2..10600966d63 100644
--- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java
@@ -232,7 +232,7 @@ public class SearchTest {
public static class DummyPatientResourceProvider implements IResourceProvider {
@Search
- public List findPatient(@OptionalParam(name = "_id") StringParam theParam) {
+ public List findPatient(@RequiredParam(name = "_id") StringParam theParam) {
ArrayList retVal = new ArrayList();
Patient patient = new Patient();
@@ -246,7 +246,7 @@ public class SearchTest {
}
@Search
- public List findPatientByAAA01(@OptionalParam(name = "AAA") StringParam theParam) {
+ public List findPatientByAAA01(@RequiredParam(name = "AAA") StringParam theParam) {
ArrayList retVal = new ArrayList();
Patient patient = new Patient();
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TransactionWithBundleParamTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TransactionWithBundleParamTest.java
new file mode 100644
index 00000000000..a9e966301f9
--- /dev/null
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TransactionWithBundleParamTest.java
@@ -0,0 +1,234 @@
+package ca.uhn.fhir.rest.server;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+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.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.Bundle;
+import ca.uhn.fhir.model.api.BundleEntry;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
+import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
+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.annotation.Transaction;
+import ca.uhn.fhir.rest.annotation.TransactionParam;
+import ca.uhn.fhir.testutil.RandomServerPortProvider;
+
+/**
+ * Created by dsotnikov on 2/25/2014.
+ */
+public class TransactionWithBundleParamTest {
+
+ private static CloseableHttpClient ourClient;
+ private static FhirContext ourCtx = new FhirContext();
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TransactionWithBundleParamTest.class);
+ private static int ourPort;
+ private static boolean ourReturnOperationOutcome;
+
+ private static Server ourServer;
+
+
+
+ @Before
+ public void before() {
+ ourReturnOperationOutcome = false;
+ }
+
+ @Test
+ public void testTransaction() throws Exception {
+ Bundle b = new Bundle();
+ InstantDt nowInstant = InstantDt.withCurrentTime();
+
+ Patient p1 = new Patient();
+ p1.addName().addFamily("Family1");
+ BundleEntry entry = b.addEntry();
+ entry.getId().setValue("1");
+ entry.setResource(p1);
+
+ Patient p2 = new Patient();
+ p2.addName().addFamily("Family2");
+ entry = b.addEntry();
+ entry.getId().setValue("2");
+ entry.setResource(p2);
+
+ BundleEntry deletedEntry = b.addEntry();
+ deletedEntry.setId(new IdDt("Patient/3"));
+ deletedEntry.setDeleted(nowInstant);
+
+ String bundleString = ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(b);
+ ourLog.info(bundleString);
+
+ HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
+ httpPost.addHeader("Accept", Constants.CT_ATOM_XML + "; pretty=true");
+ httpPost.setEntity(new StringEntity(bundleString, ContentType.create(Constants.CT_ATOM_XML, "UTF-8")));
+ HttpResponse status = ourClient.execute(httpPost);
+ String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
+
+ assertEquals(200, status.getStatusLine().getStatusCode());
+
+ ourLog.info(responseContent);
+
+ Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent);
+ assertEquals(3, bundle.size());
+
+ BundleEntry entry0 = bundle.getEntries().get(0);
+ assertEquals("http://localhost:" + ourPort + "/Patient/81", entry0.getId().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/81/_history/91", entry0.getLinkSelf().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/1", entry0.getLinkAlternate().getValue());
+
+ BundleEntry entry1 = bundle.getEntries().get(1);
+ assertEquals("http://localhost:" + ourPort + "/Patient/82", entry1.getId().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/82/_history/92", entry1.getLinkSelf().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/2", entry1.getLinkAlternate().getValue());
+
+ BundleEntry entry2 = bundle.getEntries().get(2);
+ assertEquals("http://localhost:" + ourPort + "/Patient/3", entry2.getId().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/3/_history/93", entry2.getLinkSelf().getValue());
+ assertEquals(nowInstant.getValueAsString(), entry2.getDeletedAt().getValueAsString());
+}
+
+
+ @Test
+ public void testTransactionWithOperationOutcome() throws Exception {
+ ourReturnOperationOutcome = true;
+
+ Bundle b = new Bundle();
+ InstantDt nowInstant = InstantDt.withCurrentTime();
+
+ Patient p1 = new Patient();
+ p1.addName().addFamily("Family1");
+ BundleEntry entry = b.addEntry();
+ entry.getId().setValue("1");
+ entry.setResource(p1);
+
+ Patient p2 = new Patient();
+ p2.addName().addFamily("Family2");
+ entry = b.addEntry();
+ entry.getId().setValue("2");
+ entry.setResource(p2);
+
+ BundleEntry deletedEntry = b.addEntry();
+ deletedEntry.setId(new IdDt("Patient/3"));
+ deletedEntry.setDeleted(nowInstant);
+
+ String bundleString = ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(b);
+ ourLog.info(bundleString);
+
+ HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
+ httpPost.addHeader("Accept", Constants.CT_ATOM_XML + "; pretty=true");
+ httpPost.setEntity(new StringEntity(bundleString, ContentType.create(Constants.CT_ATOM_XML, "UTF-8")));
+ HttpResponse status = ourClient.execute(httpPost);
+ String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
+
+ assertEquals(200, status.getStatusLine().getStatusCode());
+
+ ourLog.info(responseContent);
+
+ Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent);
+ assertEquals(4, bundle.size());
+
+ assertEquals(OperationOutcome.class, bundle.getEntries().get(0).getResource().getClass());
+ assertEquals("OperationOutcome (no ID)", bundle.getEntries().get(0).getTitle().getValue());
+
+ BundleEntry entry0 = bundle.getEntries().get(1);
+ assertEquals("http://localhost:" + ourPort + "/Patient/81", entry0.getId().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/81/_history/91", entry0.getLinkSelf().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/1", entry0.getLinkAlternate().getValue());
+
+ BundleEntry entry1 = bundle.getEntries().get(2);
+ assertEquals("http://localhost:" + ourPort + "/Patient/82", entry1.getId().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/82/_history/92", entry1.getLinkSelf().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/2", entry1.getLinkAlternate().getValue());
+
+ BundleEntry entry2 = bundle.getEntries().get(3);
+ assertEquals("http://localhost:" + ourPort + "/Patient/3", entry2.getId().getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/3/_history/93", entry2.getLinkSelf().getValue());
+ assertEquals(nowInstant.getValueAsString(), entry2.getDeletedAt().getValueAsString());
+}
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ ourServer.stop();
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ ourPort = RandomServerPortProvider.findFreePort();
+ ourServer = new Server(ourPort);
+
+ DummyProvider patientProvider = new DummyProvider();
+ RestfulServer server = new RestfulServer();
+ server.setProviders(patientProvider);
+
+ org.eclipse.jetty.servlet.ServletContextHandler proxyHandler = new org.eclipse.jetty.servlet.ServletContextHandler();
+ proxyHandler.setContextPath("/");
+
+ ServletHolder handler = new ServletHolder();
+ handler.setServlet(server);
+ proxyHandler.addServlet(handler, "/*");
+
+ ourServer.setHandler(proxyHandler);
+ ourServer.start();
+
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setConnectionManager(connectionManager);
+ ourClient = builder.build();
+
+ }
+
+
+
+ /**
+ * Created by dsotnikov on 2/25/2014.
+ */
+ public static class DummyProvider {
+
+ @Transaction
+ public List transaction(@TransactionParam Bundle theResources) {
+ int index=1;
+ for (IResource next : theResources.toListOfResources()) {
+ String newId = "8"+Integer.toString(index);
+ if (next.getResourceMetadata().containsKey(ResourceMetadataKeyEnum.DELETED_AT)) {
+ newId = next.getId().getIdPart();
+ }
+ next.setId(new IdDt("Patient", newId, "9"+Integer.toString(index)));
+ index++;
+ }
+
+ List retVal = theResources.toListOfResources();
+ if (ourReturnOperationOutcome) {
+ retVal = new ArrayList();
+ OperationOutcome oo = new OperationOutcome();
+ oo.addIssue().setDetails("AAAAA");
+ retVal.add(oo);
+ retVal.addAll(theResources.toListOfResources());
+ }
+
+ return retVal;
+ }
+
+
+ }
+
+}