Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transactionbuilder #2041

Merged
merged 3 commits into from Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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());


}


}