Skip to content

Commit

Permalink
Add transactionbuilder (hapifhir#2041)
Browse files Browse the repository at this point in the history
* Add transactionbuilder

* Add changelog

* Add creates
  • Loading branch information
jamesagnew authored and HananAwwad committed Nov 12, 2020
1 parent 60be745 commit 505f46e
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 16 deletions.
Expand Up @@ -32,7 +32,7 @@

/**
* This class can be used to build a Bundle resource to be used as a FHIR transaction.
*
* <p>
* This is not yet complete, and doesn't support all FHIR features. <b>USE WITH CAUTION</b> as the API
* may change.
*
Expand All @@ -52,6 +52,7 @@ public class TransactionBuilder {
private final BaseRuntimeChildDefinition myEntryRequestUrlChild;
private final BaseRuntimeChildDefinition myEntryRequestMethodChild;
private final BaseRuntimeElementDefinition<?> myEntryRequestMethodDef;
private final BaseRuntimeChildDefinition myEntryRequestIfNoneExistChild;

/**
* Constructor
Expand All @@ -77,10 +78,11 @@ public TransactionBuilder(FhirContext theContext) {
myEntryRequestDef = myEntryRequestChild.getChildByName("request");

myEntryRequestUrlChild = myEntryRequestDef.getChildByName("url");

myEntryRequestMethodChild = myEntryRequestDef.getChildByName("method");
myEntryRequestMethodDef = myEntryRequestMethodChild.getChildByName("method");

myEntryRequestIfNoneExistChild = myEntryRequestDef.getChildByName("ifNoneExist");

}

Expand All @@ -89,9 +91,47 @@ public TransactionBuilder(FhirContext theContext) {
*
* @param theResource The resource to update
*/
public TransactionBuilder addUpdateEntry(IBaseResource theResource) {
public UpdateBuilder addUpdateEntry(IBaseResource theResource) {
IBase request = addEntryAndReturnRequest(theResource);

// Bundle.entry.request.url
IPrimitiveType<?> url = (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
url.setValueAsString(theResource.getIdElement().toUnqualifiedVersionless().getValue());
myEntryRequestUrlChild.getMutator().setValue(request, url);

// Bundle.entry.request.url
IPrimitiveType<?> method = (IPrimitiveType<?>) myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments());
method.setValueAsString("PUT");
myEntryRequestMethodChild.getMutator().setValue(request, method);

return new UpdateBuilder(url);
}

/**
* Adds an entry containing an create (POST) request
*
* @param theResource The resource to create
*/
public CreateBuilder addCreateEntry(IBaseResource theResource) {
IBase request = addEntryAndReturnRequest(theResource);

String resourceType = myContext.getResourceType(theResource);

// Bundle.entry.request.url
IPrimitiveType<?> url = (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
url.setValueAsString(resourceType);
myEntryRequestUrlChild.getMutator().setValue(request, url);

// Bundle.entry.request.url
IPrimitiveType<?> method = (IPrimitiveType<?>) myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments());
method.setValueAsString("POST");
myEntryRequestMethodChild.getMutator().setValue(request, method);

return new CreateBuilder(request);
}

public IBase addEntryAndReturnRequest(IBaseResource theResource) {
Validate.notNull(theResource, "theResource must not be null");
Validate.notEmpty(theResource.getIdElement().getValue(), "theResource must have an ID");

IBase entry = myEntryDef.newInstance();
myEntryChild.getMutator().addValue(myBundle, entry);
Expand All @@ -107,24 +147,47 @@ public TransactionBuilder addUpdateEntry(IBaseResource theResource) {
// Bundle.entry.request
IBase request = myEntryRequestDef.newInstance();
myEntryRequestChild.getMutator().setValue(entry, request);
return request;
}

// Bundle.entry.request.url
IPrimitiveType<?> url = (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
url.setValueAsString(theResource.getIdElement().toUnqualifiedVersionless().getValue());
myEntryRequestUrlChild.getMutator().setValue(request, url);

// Bundle.entry.request.url
IPrimitiveType<?> method = (IPrimitiveType<?>) myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments());
method.setValueAsString("PUT");
myEntryRequestMethodChild.getMutator().setValue(request, method);

return this;
public IBaseBundle getBundle() {
return myBundle;
}

public class UpdateBuilder {

private final IPrimitiveType<?> myUrl;

public UpdateBuilder(IPrimitiveType<?> theUrl) {
myUrl = theUrl;
}

/**
* Make this update a Conditional Update
*/
public void conditional(String theConditionalUrl) {
myUrl.setValueAsString(theConditionalUrl);
}

public IBaseBundle getBundle() {
return myBundle;
}

public class CreateBuilder {
private final IBase myRequest;

public CreateBuilder(IBase theRequest) {
myRequest = theRequest;
}

/**
* Make this create a Conditional Create
*/
public void conditional(String theConditionalUrl) {
IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) myContext.getElementDefinition("string").newInstance();
ifNoneExist.setValueAsString(theConditionalUrl);

myEntryRequestIfNoneExistChild.getMutator().setValue(myRequest, ifNoneExist);
}

}
}
@@ -0,0 +1,107 @@
package ca.uhn.hapi.fhir.docs;

/*-
* #%L
* HAPI FHIR - Docs
* %%
* Copyright (C) 2014 - 2020 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.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.util.TransactionBuilder;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r4.model.Patient;

@SuppressWarnings("unused")
public class TransactionBuilderExamples {

private FhirContext myFhirContext;
private IGenericClient myFhirClient;

public void update() throws FHIRException {
//START SNIPPET: update
// Create a TransactionBuilder
TransactionBuilder builder = new TransactionBuilder(myFhirContext);

// Create a Patient to update
Patient patient = new Patient();
patient.setId("http://foo/Patient/123");
patient.setActive(true);

// Add the patient as an update (aka PUT) to the Bundle
builder.addUpdateEntry(patient);

// Execute the transaction
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
//END SNIPPET: update
}

public void updateConditional() throws FHIRException {
//START SNIPPET: updateConditional
// Create a TransactionBuilder
TransactionBuilder builder = new TransactionBuilder(myFhirContext);

// Create a Patient to update
Patient patient = new Patient();
patient.setActive(true);
patient.addIdentifier().setSystem("http://foo").setValue("bar");

// Add the patient as an update (aka PUT) to the Bundle
builder.addUpdateEntry(patient).conditional("Patient?identifier=http://foo|bar");

// Execute the transaction
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
//END SNIPPET: updateConditional
}

public void create() throws FHIRException {
//START SNIPPET: create
// Create a TransactionBuilder
TransactionBuilder builder = new TransactionBuilder(myFhirContext);

// Create a Patient to create
Patient patient = new Patient();
patient.setActive(true);

// Add the patient as a create (aka POST) to the Bundle
builder.addCreateEntry(patient);

// Execute the transaction
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
//END SNIPPET: create
}

public void createConditional() throws FHIRException {
//START SNIPPET: createConditional
// Create a TransactionBuilder
TransactionBuilder builder = new TransactionBuilder(myFhirContext);

// Create a Patient to create
Patient patient = new Patient();
patient.setActive(true);
patient.addIdentifier().setSystem("http://foo").setValue("bar");

// Add the patient as a create (aka POST) to the Bundle
builder.addCreateEntry(patient).conditional("Patient?identifier=http://foo|bar");

// Execute the transaction
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
//END SNIPPET: createConditional
}

}
@@ -0,0 +1,4 @@
---
type: add
issue: 2041
title: "A new class called TransactionBuilder has been added. This class can be used to build FHIR Transaction Bundles easily."
Expand Up @@ -15,6 +15,7 @@ page.model.profiles_and_extensions=Profiles and Extensions
page.model.converter=Version Converters
page.model.custom_structures=Custom Structures
page.model.narrative_generation=Narrative Generation
page.model.transaction_builder=Transaction Builder

section.client.title=Client
page.client.introduction=Introduction
Expand Down
@@ -0,0 +1,39 @@
# Transaction Builder

The TransactionBuilder ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/util/TransactionBuilder.html)) can be used to construct FHIR Transaction Bundles.

Note that this class is a work in progress! It does not yet support all transaction features. We will add more features over time, and document them here. Pull requests are welcomed.

# Resource Create

To add an update (aka PUT) operation to a transaction bundle

```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/TransactionBuilderExamples.java|create}}
```

## Conditional Create

If you want to perform a conditional create:

```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/TransactionBuilderExamples.java|createConditional}}
```

# Resource Updates

To add an update (aka PUT) operation to a transaction bundle

```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/TransactionBuilderExamples.java|update}}
```

## Conditional Update

If you want to perform a conditional update:

```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/TransactionBuilderExamples.java|updateConditional}}
```


Expand Up @@ -37,4 +37,73 @@ public void testAddEntryUpdate() {

}


@Test
public void testAddEntryUpdateConditional() {
TransactionBuilder builder = new TransactionBuilder(myFhirContext);

Patient patient = new Patient();
patient.setId("http://foo/Patient/123");
patient.setActive(true);
builder.addUpdateEntry(patient).conditional("Patient?active=true");

Bundle bundle = (Bundle) builder.getBundle();
ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));

assertEquals(Bundle.BundleType.TRANSACTION, bundle.getType());
assertEquals(1, bundle.getEntry().size());
assertSame(patient, bundle.getEntry().get(0).getResource());
assertEquals("http://foo/Patient/123", bundle.getEntry().get(0).getFullUrl());
assertEquals("Patient?active=true", bundle.getEntry().get(0).getRequest().getUrl());
assertEquals(Bundle.HTTPVerb.PUT, bundle.getEntry().get(0).getRequest().getMethod());


}


@Test
public void testAddEntryCreate() {
TransactionBuilder builder = new TransactionBuilder(myFhirContext);

Patient patient = new Patient();
patient.setActive(true);
builder.addCreateEntry(patient);

Bundle bundle = (Bundle) builder.getBundle();
ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));

assertEquals(Bundle.BundleType.TRANSACTION, bundle.getType());
assertEquals(1, bundle.getEntry().size());
assertSame(patient, bundle.getEntry().get(0).getResource());
assertEquals(null, bundle.getEntry().get(0).getFullUrl());
assertEquals("Patient", bundle.getEntry().get(0).getRequest().getUrl());
assertEquals(Bundle.HTTPVerb.POST, bundle.getEntry().get(0).getRequest().getMethod());


}


@Test
public void testAddEntryCreateConditional() {
TransactionBuilder builder = new TransactionBuilder(myFhirContext);

Patient patient = new Patient();
patient.setActive(true);
builder.addCreateEntry(patient).conditional("Patient?active=true");

Bundle bundle = (Bundle) builder.getBundle();
ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));

assertEquals(Bundle.BundleType.TRANSACTION, bundle.getType());
assertEquals(1, bundle.getEntry().size());
assertSame(patient, bundle.getEntry().get(0).getResource());
assertEquals(null, bundle.getEntry().get(0).getFullUrl());
assertEquals("Patient", bundle.getEntry().get(0).getRequest().getUrl());
assertEquals("Patient?active=true", bundle.getEntry().get(0).getRequest().getIfNoneExist());
assertEquals(Bundle.HTTPVerb.POST, bundle.getEntry().get(0).getRequest().getMethod());


}


}

0 comments on commit 505f46e

Please sign in to comment.