From fd41bafa82c0652d61ed19f0f9ff6d3be5213292 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Sun, 17 Aug 2014 22:58:30 -0400 Subject: [PATCH] Allow bundle as param in transaction method --- .../java/example/ServerMetadataExamples.java | 2 +- hapi-fhir-base/src/changes/changes.xml | 4 + .../ca/uhn/fhir/model/view/ViewGenerator.java | 20 ++ .../rest/annotation/TransactionParam.java | 8 + .../rest/method/TransactionMethodBinding.java | 48 ++-- .../rest/method/TransactionParamBinder.java | 38 ++- .../ca/uhn/fhir/rest/param/BaseParam.java | 20 ++ .../main/java/ca/uhn/fhir/util/UrlUtil.java | 20 ++ .../src/site/xdoc/doc_rest_operations.xml | 5 + .../rest/client/TransactionClientTest.java | 53 +++- .../ca/uhn/fhir/rest/server/SearchTest.java | 4 +- .../TransactionWithBundleParamTest.java | 234 ++++++++++++++++++ 12 files changed, 421 insertions(+), 35 deletions(-) create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TransactionWithBundleParamTest.java diff --git a/hapi-fhir-base/examples/src/main/java/example/ServerMetadataExamples.java b/hapi-fhir-base/examples/src/main/java/example/ServerMetadataExamples.java index fa8350e0142..d9f5621e3a8 100644 --- a/hapi-fhir-base/examples/src/main/java/example/ServerMetadataExamples.java +++ b/hapi-fhir-base/examples/src/main/java/example/ServerMetadataExamples.java @@ -34,7 +34,7 @@ public class ServerMetadataExamples { String linkAlternate = "Patient/7736"; ResourceMetadataKeyEnum.LINK_ALTERNATE.put(patient, linkAlternate); String linkSearch = "Patient?name=smith&name=john"; - ResourceMetadataKeyEnum.LINK_ALTERNATE.put(patient, linkSearch); + ResourceMetadataKeyEnum.LINK_SEARCH.put(patient, linkSearch); // Set the published and updated dates InstantDt pubDate = new InstantDt("2011-02-22"); diff --git a/hapi-fhir-base/src/changes/changes.xml b/hapi-fhir-base/src/changes/changes.xml index 4e92efae0f4..7dbf5aeaac1 100644 --- a/hapi-fhir-base/src/changes/changes.xml +++ b/hapi-fhir-base/src/changes/changes.xml @@ -83,6 +83,10 @@ Category header (for tags) is correctly read in client for "read" operation + + Transaction method in server can now have parameter type Bundle instead of + List<IResource> + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/view/ViewGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/view/ViewGenerator.java index afa06b84f47..27807d57331 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/view/ViewGenerator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/view/ViewGenerator.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.model.view; +/* + * #%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 java.util.List; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/TransactionParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/TransactionParam.java index 63c854f7a08..415562a5723 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/TransactionParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/TransactionParam.java @@ -24,7 +24,15 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.List; +import ca.uhn.fhir.model.api.Bundle; + +/** + * Parameter annotation for the "transaction" operation. The parameter annotated with this + * annotation must be either of type {@link Bundle} or of type + * {@link List}<IResource> + */ @Target(value=ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface TransactionParam { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java index e95f3f4cc6a..60a61a736ea 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java @@ -52,9 +52,9 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding public TransactionMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) { super(null, theMethod, theConetxt, theProvider); - + myTransactionParamIndex = -1; - int index=0; + int index = 0; for (IParameter next : getParameters()) { if (next instanceof TransactionParamBinder) { myTransactionParamIndex = index; @@ -62,7 +62,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding index++; } - if (myTransactionParamIndex==-1) { + if (myTransactionParamIndex == -1) { throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a parameter annotated with the @" + TransactionParam.class + " annotation"); } } @@ -72,7 +72,6 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding return RestfulOperationSystemEnum.TRANSACTION; } - @Override public boolean incomingServerRequestMatchesMethod(Request theRequest) { if (theRequest.getRequestType() != RequestType.POST) { @@ -95,16 +94,23 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding @SuppressWarnings("unchecked") @Override public IBundleProvider invokeServer(Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { - List resources = (List) theMethodParams[myTransactionParamIndex]; - - List oldIds= new ArrayList(); + + // Grab the IDs of all of the resources in the transaction + List resources; + if (theMethodParams[myTransactionParamIndex] instanceof Bundle) { + resources = ((Bundle) theMethodParams[myTransactionParamIndex]).toListOfResources(); + } else { + resources = (List) theMethodParams[myTransactionParamIndex]; + } + List oldIds = new ArrayList(); for (IResource next : resources) { oldIds.add(next.getId()); } - - Object response= invokeServerMethod(theMethodParams); + + // Call the server implementation method + Object response = invokeServerMethod(theMethodParams); IBundleProvider retVal = toResourceList(response); - + int offset = 0; if (retVal.size() != resources.size()) { if (retVal.size() > 0 && retVal.getResources(0, 1).get(0) instanceof OperationOutcome) { @@ -113,22 +119,22 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding throw new InternalErrorException("Transaction bundle contained " + resources.size() + " entries, but server method response contained " + retVal.size() + " entries (must be the same)"); } } - - List retResources = retVal.getResources(offset, retVal.size()); - for (int i =0; i < resources.size(); i++) { + + List retResources = retVal.getResources(offset, retVal.size()); + for (int i = 0; i < resources.size(); i++) { IdDt oldId = oldIds.get(i); IResource newRes = retResources.get(i); if (newRes.getId() == null || newRes.getId().isEmpty()) { throw new InternalErrorException("Transaction method returned resource at index " + i + " with no id specified - IResource#setId(IdDt)"); } - + if (oldId != null && !oldId.isEmpty()) { if (!oldId.equals(newRes.getId())) { newRes.getResourceMetadata().put(ResourceMetadataKeyEnum.PREVIOUS_ID, oldId); } } } - + return retVal; } @@ -147,11 +153,15 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding @Override public BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { - @SuppressWarnings("unchecked") - List resources = (List) theArgs[myTransactionParamIndex]; FhirContext context = getContext(); - - return createTransactionInvocation(resources, context); + if (theArgs[myTransactionParamIndex] instanceof Bundle) { + Bundle bundle = (Bundle) theArgs[myTransactionParamIndex]; + return createTransactionInvocation(bundle, context); + } else { + @SuppressWarnings("unchecked") + List resources = (List) theArgs[myTransactionParamIndex]; + return createTransactionInvocation(resources, context); + } } public static BaseHttpClientInvocation createTransactionInvocation(List theResources, FhirContext theContext) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionParamBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionParamBinder.java index 0f4ef87a5c9..22e23b9d6e2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionParamBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionParamBinder.java @@ -38,40 +38,56 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; class TransactionParamBinder implements IParameter { + private boolean myParamIsBundle; + public TransactionParamBinder() { } - + @Override public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map> theTargetQueryArguments, BaseHttpClientInvocation theClientInvocation) throws InternalErrorException { // TODO Auto-generated method stub - + } @Override public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException { Bundle resource = (Bundle) theRequestContents; - + if (myParamIsBundle) { + return resource; + } + ArrayList retVal = new ArrayList(); for (BundleEntry next : resource.getEntries()) { if (next.getResource() != null) { retVal.add(next.getResource()); } } - + return retVal; } @Override public void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> 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; + } + + + } + +}