Skip to content

Commit

Permalink
Merge pull request #1582 from IBM/issue-1504
Browse files Browse the repository at this point in the history
Signed-off-by: Lee Surprenant <lmsurpre@us.ibm.com>
  • Loading branch information
lmsurpre authored Oct 15, 2020
2 parents 35ea4b5 + 920ca45 commit 86d6299
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public interface FHIRPersistence {
* an OperationOutcome with hints, warnings, or errors related to the interaction
* @throws FHIRPersistenceException
*/
public <T extends Resource> SingleResourceResult<T> create(FHIRPersistenceContext context, T resource) throws FHIRPersistenceException;
<T extends Resource> SingleResourceResult<T> create(FHIRPersistenceContext context, T resource) throws FHIRPersistenceException;

/**
* Retrieves the most recent version of a FHIR Resource from the datastore.
Expand All @@ -40,7 +40,7 @@ public interface FHIRPersistence {
* an OperationOutcome with hints, warnings, or errors related to the interaction
* @throws FHIRPersistenceException
*/
public <T extends Resource> SingleResourceResult<T> read(FHIRPersistenceContext context, Class<T> resourceType, String logicalId)
<T extends Resource> SingleResourceResult<T> read(FHIRPersistenceContext context, Class<T> resourceType, String logicalId)
throws FHIRPersistenceException;

/**
Expand All @@ -54,7 +54,7 @@ public <T extends Resource> SingleResourceResult<T> read(FHIRPersistenceContext
* an OperationOutcome with hints, warnings, or errors related to the interaction
* @throws FHIRPersistenceException
*/
public <T extends Resource> SingleResourceResult<T> vread(FHIRPersistenceContext context, Class<T> resourceType, String logicalId, String versionId)
<T extends Resource> SingleResourceResult<T> vread(FHIRPersistenceContext context, Class<T> resourceType, String logicalId, String versionId)
throws FHIRPersistenceException;

/**
Expand All @@ -67,7 +67,7 @@ public <T extends Resource> SingleResourceResult<T> vread(FHIRPersistenceContext
* an OperationOutcome with hints, warnings, or errors related to the interaction
* @throws FHIRPersistenceException
*/
public <T extends Resource> SingleResourceResult<T> update(FHIRPersistenceContext context, String logicalId, T resource) throws FHIRPersistenceException;
<T extends Resource> SingleResourceResult<T> update(FHIRPersistenceContext context, String logicalId, T resource) throws FHIRPersistenceException;

/**
* Deletes the specified FHIR Resource from the datastore.
Expand All @@ -79,7 +79,7 @@ public <T extends Resource> SingleResourceResult<T> vread(FHIRPersistenceContext
* an OperationOutcome with hints, warnings, or errors related to the interaction
* @throws FHIRPersistenceException
*/
public default <T extends Resource> SingleResourceResult<T> delete(FHIRPersistenceContext context, Class<T> resourceType, String logicalId) throws FHIRPersistenceException {
default <T extends Resource> SingleResourceResult<T> delete(FHIRPersistenceContext context, Class<T> resourceType, String logicalId) throws FHIRPersistenceException {
throw new FHIRPersistenceNotSupportedException("The 'delete' operation is not supported by this persistence implementation");
}

Expand All @@ -93,7 +93,7 @@ public default <T extends Resource> SingleResourceResult<T> delete(FHIRPersisten
* an OperationOutcome with hints, warnings, or errors related to the interaction
* @throws FHIRPersistenceException
*/
public <T extends Resource> MultiResourceResult<T> history(FHIRPersistenceContext context, Class<T> resourceType, String logicalId) throws FHIRPersistenceException;
<T extends Resource> MultiResourceResult<T> history(FHIRPersistenceContext context, Class<T> resourceType, String logicalId) throws FHIRPersistenceException;

/**
* Performs a search on the specified target resource type using the specified search parameters.
Expand All @@ -104,30 +104,30 @@ public default <T extends Resource> SingleResourceResult<T> delete(FHIRPersisten
* an OperationOutcome with hints, warnings, or errors related to the interaction
* @throws FHIRPersistenceException
*/
public MultiResourceResult<Resource> search(FHIRPersistenceContext context, Class<? extends Resource> resourceType) throws FHIRPersistenceException;
MultiResourceResult<Resource> search(FHIRPersistenceContext context, Class<? extends Resource> resourceType) throws FHIRPersistenceException;

/**
* Returns true iff the persistence layer implementation supports transactions.
*/
public boolean isTransactional();
boolean isTransactional();

/**
* Returns an OperationOutcome indicating the current status of the persistence store / backend
* @return An OperationOutcome with a list of 0 or more OperationalOutcomeIssue indicating the status of the underlying datastore
* @throws FHIRPersistenceException
*/
public OperationOutcome getHealth() throws FHIRPersistenceException;
OperationOutcome getHealth() throws FHIRPersistenceException;

/**
* Returns a FHIRPersistenceTransaction object associated with the persistence layer implementation in use.
* This can then be used to control transactional boundaries.
*/
public FHIRPersistenceTransaction getTransaction();
FHIRPersistenceTransaction getTransaction();

/**
* Returns true iff the persistence layer implementation supports the "delete" operation.
*/
public default boolean isDeleteSupported() {
default boolean isDeleteSupported() {
return false;
}

Expand All @@ -136,5 +136,5 @@ public default boolean isDeleteSupported() {
*
* @return resource ID
*/
public String generateResourceId();
String generateResourceId();
}
165 changes: 151 additions & 14 deletions fhir-server/src/main/java/com/ibm/fhir/server/util/FHIRRestHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -151,7 +155,9 @@ public FHIRRestOperationResponse doCreate(String type, Resource resource, String

// Check to see if we're supposed to perform a conditional 'create'.
if (ifNoneExist != null && !ifNoneExist.isEmpty()) {
log.fine("Performing conditional create with search criteria: " + ifNoneExist);
if (log.isLoggable(Level.FINE)) {
log.fine("Performing conditional create with search criteria: " + ifNoneExist);
}
Bundle responseBundle = null;

// Perform the search using the "If-None-Exist" header value.
Expand All @@ -170,7 +176,9 @@ public FHIRRestOperationResponse doCreate(String type, Resource resource, String

// Check the search results to determine whether or not to perform the create operation.
int resultCount = responseBundle.getEntry().size();
log.fine("Conditional create search yielded " + resultCount + " results.");
if (log.isLoggable(Level.FINE)) {
log.fine("Conditional create search yielded " + resultCount + " results.");
}

if (resultCount == 0) {
// Do nothing and fall through to process the 'create' request.
Expand All @@ -183,7 +191,9 @@ public FHIRRestOperationResponse doCreate(String type, Resource resource, String
ior.setResource(matchedResource);
ior.setOperationOutcome(FHIRUtil.buildOperationOutcome("Found a single match; check the Location header",
IssueType.INFORMATIONAL, IssueSeverity.INFORMATION));
log.fine("Returning location URI of matched resource: " + ior.getLocationURI());
if (log.isLoggable(Level.FINE)) {
log.fine("Returning location URI of matched resource: " + ior.getLocationURI());
}
return ior;
} else {
String msg =
Expand Down Expand Up @@ -1181,7 +1191,7 @@ private Bundle validateBundle(Bundle bundle) throws Exception {

List<Bundle.Entry> responseList = new ArrayList<Bundle.Entry>();

List<String> localIdentifiers = new ArrayList<>();
Set<String> localIdentifiers = new HashSet<>();

for (Bundle.Entry requestEntry : bundle.getEntry()) {
// Create a corresponding response entry and add it to the response bundle.
Expand Down Expand Up @@ -1360,7 +1370,9 @@ private void methodValidation(HTTPVerb method, Resource resource) throws FHIRPer
private void performVersionAwareUpdateCheck(Resource currentResource, String ifMatchValue)
throws FHIROperationException {
if (ifMatchValue != null) {
log.fine("Performing a version aware update. ETag value = " + ifMatchValue);
if (log.isLoggable(Level.FINE)) {
log.fine("Performing a version aware update. ETag value = " + ifMatchValue);
}

String ifMatchVersion = getVersionIdFromETagValue(ifMatchValue);

Expand All @@ -1371,7 +1383,9 @@ private void performVersionAwareUpdateCheck(Resource currentResource, String ifM
+ ifMatchValue, IssueType.PROCESSING);
}

log.fine("Version id from ETag value specified in request: " + ifMatchVersion);
if (log.isLoggable(Level.FINE)) {
log.fine("Version id from ETag value specified in request: " + ifMatchVersion);
}

// Retrieve the version #'s from the current and updated resources.
String currentVersion = null;
Expand Down Expand Up @@ -1471,16 +1485,20 @@ private Bundle processBundleEntries(Bundle requestBundle, Bundle responseBundle,

// Generate a request correlation id for this request bundle.
bundleRequestCorrelationId = UUID.randomUUID().toString();
log.fine("Processing request bundle, request-correlation-id=" + bundleRequestCorrelationId);
if (log.isLoggable(Level.FINE)) {
log.fine("Processing request bundle, request-correlation-id=" + bundleRequestCorrelationId);
}

try {
// If we're working on a 'transaction' type interaction, then start a new transaction now
if (responseBundle.getType() == BundleType.TRANSACTION_RESPONSE) {
bundleTransactionCorrelationId = bundleRequestCorrelationId;
txn = new FHIRTransactionHelper(getTransaction());
txn.begin();
log.fine("Started new transaction for transaction bundle, txn-correlation-id="
if (log.isLoggable(Level.FINE)) {
log.fine("Started new transaction for transaction bundle, txn-correlation-id="
+ bundleTransactionCorrelationId);
}
}

// Build a mapping of local identifiers to external identifiers for local reference resolution.
Expand All @@ -1498,16 +1516,20 @@ private Bundle processBundleEntries(Bundle requestBundle, Bundle responseBundle,

// Commit transaction if started
if (txn != null) {
log.fine("Committing transaction for transaction bundle, txn-correlation-id="
if (log.isLoggable(Level.FINE)) {
log.fine("Committing transaction for transaction bundle, txn-correlation-id="
+ bundleTransactionCorrelationId);
}
txn.commit();
txn = null;
}
return responseBundle;

} finally {
log.fine("Finished processing request bundle, request-correlation-id="
if (log.isLoggable(Level.FINE)) {
log.fine("Finished processing request bundle, request-correlation-id="
+ bundleRequestCorrelationId);
}

// Clear both correlation id fields since we're done processing the bundle.
bundleRequestCorrelationId = null;
Expand Down Expand Up @@ -1549,10 +1571,27 @@ private Bundle processEntriesForMethod(Bundle requestBundle, Bundle responseBund
log.entering(this.getClass().getName(), "processEntriesForMethod", new Object[] {"httpMethod", httpMethod });

try {
// Visit each of the request entries, processing those with the specified request method.
// First, obtain a list of request entry indices for the entries that we'll process.
// This list will contain the indices associated with only the entries for the specified http method.
List<Integer> entryIndices =
getBundleRequestIndicesForMethod(requestBundle, responseBundle, httpMethod);
if (log.isLoggable(Level.FINER)) {
log.finer("Bundle request indices to be processed: " + entryIndices.toString());
}

// Next, for PUT and DELETE requests, we need to sort the indices by the request url path value.
if (httpMethod.equals(HTTPVerb.PUT) || httpMethod.equals(HTTPVerb.DELETE)) {
sortBundleRequestEntries(requestBundle, entryIndices);
if (log.isLoggable(Level.FINER)) {
log.finer("Sorted bundle request indices to be processed: "
+ entryIndices.toString());
}
}

// Now visit each of the request entries using the list of indices obtained above.
// Use hashmap to store both the index and the accordingly updated response bundle entry.
Map<Integer, Bundle.Entry> responseIndexAndEntries = new HashMap<Integer, Bundle.Entry>();
for (int entryIndex=0; entryIndex<requestBundle.getEntry().size(); ++entryIndex) {
for (Integer entryIndex : entryIndices) {
Bundle.Entry requestEntry = requestBundle.getEntry().get(entryIndex);
Bundle.Entry.Request request = requestEntry.getRequest();
Bundle.Entry responseEntry = responseBundle.getEntry().get(entryIndex);
Expand Down Expand Up @@ -2023,6 +2062,100 @@ private Bundle reconstructResponseBundle(Bundle responseBundle,
return responseBundle;
}

/**
* Returns a list of Integers that provide the indices of the bundle entries associated with the specified http
* method.
*
* @param requestBundle
* the request bundle
* @param httpMethod
* the http method to look for
* @return
*/
private List<Integer> getBundleRequestIndicesForMethod(Bundle requestBundle,
Bundle responseBundle, HTTPVerb httpMethod) {
List<Integer> indices = new ArrayList<>();
for (int i = 0; i < requestBundle.getEntry().size(); i++) {
Bundle.Entry requestEntry = requestBundle.getEntry().get(i);
Bundle.Entry.Request request = requestEntry.getRequest();

Bundle.Entry responseEntry = responseBundle.getEntry().get(i);
Bundle.Entry.Response response = responseEntry.getResponse();

// If the response status is SC_OK which means the request passed the validation,
// and this request entry's http method is the one we're looking for,
// then record the index in our list.
// (please notice that status can not be null since R4, So we set the response status as SC_OK
// after the resource validation. )
if (response.getStatus().equals(SC_OK_STRING)
&& request.getMethod().equals(httpMethod)) {
indices.add(Integer.valueOf(i));
}
}
return indices;
}

/**
* This function sorts the request entries in the specified bundle, based on the path part of the entry's 'url'
* field.
*
* @param bundle
* the bundle containing the request entries to be sorted.
* @return an array of Integer which provides the "sorted" ordering of request entry index values.
*/
private void sortBundleRequestEntries(Bundle bundle, List<Integer> indices) {
// Sort the list of indices based on the contents of their entries in the bundle.
Collections.sort(indices, new BundleEntryComparator(bundle.getEntry()));
}

private static class BundleEntryComparator implements Comparator<Integer> {
private List<Bundle.Entry> entries;

public BundleEntryComparator(List<Bundle.Entry> entries) {
this.entries = entries;
}

@Override
public int compare(Integer indexA, Integer indexB) {
Bundle.Entry a = entries.get(indexA);
Bundle.Entry b = entries.get(indexB);
String pathA = getUrlPath(a);
String pathB = getUrlPath(b);

if (log.isLoggable(Level.FINE)) {
log.fine("Comparing request entry URL paths: " + pathA + ", " + pathB);
}
if (pathA != null && pathB != null) {
return pathA.compareTo(pathB);
} else if (pathA != null) {
return 1;
} else if (pathB != null) {
return -1;
}
return 0;
}
}

/**
* Returns the specified BundleEntry's path component of the 'url' field.
*
* @param entry
* the bundle entry
* @return the bundle entry's 'url' field's path component
*/
private static String getUrlPath(Bundle.Entry entry) {
String path = null;
Bundle.Entry.Request request = entry.getRequest();
if (request != null) {
if (request.getUrl() != null && request.getUrl().getValue() != null) {
FHIRUrlParser requestURL = new FHIRUrlParser(request.getUrl().getValue());
path = requestURL.getPath();
}
}

return path;
}

/**
* This function converts the specified query string (a String) into an equivalent MultivaluedMap<String,String>
* containing the query parameters defined in the query string.
Expand Down Expand Up @@ -2112,7 +2245,9 @@ private void addLocalRefMapping(Map<String, String> localRefMap, String localIde
externalIdentifier = ModelSupport.getTypeName(resource.getClass()) + "/" + resource.getId();
}
localRefMap.put(localIdentifier, externalIdentifier);
log.finer("Added local/ext identifier mapping: " + localIdentifier + " --> " + externalIdentifier);
if (log.isLoggable(Level.FINER)) {
log.finer("Added local/ext identifier mapping: " + localIdentifier + " --> " + externalIdentifier);
}
}
}

Expand All @@ -2130,7 +2265,9 @@ private String retrieveLocalIdentifier(Bundle.Entry requestEntry) {
String fullUrl = requestEntry.getFullUrl().getValue();
if (fullUrl != null && fullUrl.startsWith(LOCAL_REF_PREFIX)) {
localIdentifier = fullUrl;
log.finer("Request entry contains local identifier: " + localIdentifier);
if (log.isLoggable(Level.FINER)) {
log.finer("Request entry contains local identifier: " + localIdentifier);
}
}
}
return localIdentifier;
Expand Down
Loading

0 comments on commit 86d6299

Please sign in to comment.