Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
jamesagnew 2018-04-24 19:14:56 -04:00
commit e299b062a6
17 changed files with 298 additions and 61 deletions

View File

@ -6,7 +6,8 @@ import org.apache.commons.lang3.time.DateUtils;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -20,9 +21,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* 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.
@ -48,9 +49,8 @@ public class StopWatch {
private static final NumberFormat TEN_DAY_FORMAT = new DecimalFormat("0");
private static Long ourNowForUnitTest;
private long myStarted = now();
private long myCurrentTaskStarted = -1L;
private LinkedHashMap<String, Long> myTaskTotals;
private String myCurrentTaskName;
private TaskTiming myCurrentTask;
private LinkedList<TaskTiming> myTasks;
/**
* Constructor
@ -68,9 +68,9 @@ public class StopWatch {
myStarted = theStart.getTime();
}
private void ensureTaskTotalsMapExists() {
if (myTaskTotals == null) {
myTaskTotals = new LinkedHashMap<>();
private void addNewlineIfContentExists(StringBuilder theB) {
if (theB.length() > 0) {
theB.append("\n");
}
}
@ -80,14 +80,17 @@ public class StopWatch {
* is currently started so it's ok to call it more than once.
*/
public void endCurrentTask() {
if (isNotBlank(myCurrentTaskName)) {
ensureTaskTotalsMapExists();
Long existingTotal = myTaskTotals.get(myCurrentTaskName);
long taskTimeElapsed = now() - myCurrentTaskStarted;
Long newTotal = existingTotal != null ? existingTotal + taskTimeElapsed : taskTimeElapsed;
myTaskTotals.put(myCurrentTaskName, newTotal);
ensureTasksListExists();
if (myCurrentTask != null) {
myCurrentTask.setEnd(now());
}
myCurrentTask = null;
}
private void ensureTasksListExists() {
if (myTasks == null) {
myTasks = new LinkedList<>();
}
myCurrentTaskName = null;
}
/**
@ -95,23 +98,49 @@ public class StopWatch {
*/
public String formatTaskDurations() {
// Flush the current task if it's ongoing
String continueTask = myCurrentTaskName;
if (isNotBlank(myCurrentTaskName)) {
endCurrentTask();
startTask(continueTask);
ensureTasksListExists();
StringBuilder b = new StringBuilder();
if (myTasks.size() > 0) {
long delta = myTasks.getFirst().getStart() - myStarted;
if (delta > 10) {
addNewlineIfContentExists(b);
b.append("Before first task");
b.append(": ");
b.append(formatMillis(delta));
}
}
ensureTaskTotalsMapExists();
StringBuilder b = new StringBuilder();
for (String nextTask : myTaskTotals.keySet()) {
if (b.length() > 0) {
b.append("\n");
TaskTiming last = null;
for (TaskTiming nextTask : myTasks) {
if (last != null) {
long delta = nextTask.getStart() - last.getEnd();
if (delta > 10) {
addNewlineIfContentExists(b);
b.append("Between");
b.append(": ");
b.append(formatMillis(delta));
}
}
b.append(nextTask);
addNewlineIfContentExists(b);
b.append(nextTask.getTaskName());
b.append(": ");
b.append(formatMillis(myTaskTotals.get(nextTask)));
long delta = nextTask.getMillis();
b.append(formatMillis(delta));
last = nextTask;
}
if (myTasks.size() > 0) {
long delta = now() - myTasks.getLast().getEnd();
if (delta > 10) {
addNewlineIfContentExists(b);
b.append("After last task");
b.append(": ");
b.append(formatMillis(delta));
}
}
return b.toString();
@ -214,9 +243,11 @@ public class StopWatch {
public void startTask(String theTaskName) {
endCurrentTask();
if (isNotBlank(theTaskName)) {
myCurrentTaskStarted = now();
myCurrentTask = new TaskTiming()
.setTaskName(theTaskName)
.setStart(now());
myTasks.add(myCurrentTask);
}
myCurrentTaskName = theTaskName;
}
/**
@ -301,4 +332,44 @@ public class StopWatch {
ourNowForUnitTest = theNowForUnitTest;
}
private static class TaskTiming {
private long myStart;
private long myEnd;
private String myTaskName;
public long getEnd() {
if (myEnd == 0) {
return now();
}
return myEnd;
}
public TaskTiming setEnd(long theEnd) {
myEnd = theEnd;
return this;
}
public long getMillis() {
return getEnd() - getStart();
}
public long getStart() {
return myStart;
}
public TaskTiming setStart(long theStart) {
myStart = theStart;
return this;
}
public String getTaskName() {
return myTaskName;
}
public TaskTiming setTaskName(String theTaskName) {
myTaskName = theTaskName;
return this;
}
}
}

View File

@ -122,6 +122,7 @@ public class StopWatchTest {
StopWatch.setNowForUnitTestForUnitTest(1600L);
String taskDurations = sw.formatTaskDurations();
ourLog.info(taskDurations);
assertEquals("TASK1: 500ms\nTASK2: 100ms", taskDurations);
}

View File

@ -159,6 +159,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
ourLog.debug("Beginning {} with {} resources", theActionName, theRequest.getEntry().size());
final Date updateTime = new Date();
final StopWatch transactionStopWatch = new StopWatch();
final Set<IdType> allIds = new LinkedHashSet<>();
final Map<IdType, IdType> idSubstitutions = new HashMap<>();
@ -221,9 +222,14 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
Map<BundleEntryComponent, ResourceTable> entriesToProcess = txManager.execute(new TransactionCallback<Map<BundleEntryComponent, ResourceTable>>() {
@Override
public Map<BundleEntryComponent, ResourceTable> doInTransaction(TransactionStatus status) {
return doTransactionWriteOperations(theRequestDetails, theRequest, theActionName, updateTime, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries);
Map<BundleEntryComponent, ResourceTable> retVal = doTransactionWriteOperations(theRequestDetails, theActionName, updateTime, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries, transactionStopWatch);
transactionStopWatch.startTask("Commit writes to database");
return retVal;
}
});
transactionStopWatch.endCurrentTask();
for (Entry<BundleEntryComponent, ResourceTable> nextEntry : entriesToProcess.entrySet()) {
String responseLocation = nextEntry.getValue().getIdDt().toUnqualified().getValue();
String responseEtag = nextEntry.getValue().getIdDt().getVersionIdPart();
@ -234,6 +240,9 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
/*
* Loop through the request and process any entries of type GET
*/
if (getEntries.size() > 0) {
transactionStopWatch.startTask("Process " + getEntries.size() + " GET entries");
}
for (BundleEntryComponent nextReqEntry : getEntries) {
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
BundleEntryComponent nextRespEntry = response.getEntry().get(originalOrder);
@ -247,7 +256,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
int qIndex = url.indexOf('?');
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
requestDetails.setParameters(new HashMap<String, String[]>());
requestDetails.setParameters(new HashMap<>());
if (qIndex != -1) {
String params = url.substring(qIndex);
List<NameValuePair> parameters = translateMatchUrl(params);
@ -297,13 +306,17 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
}
transactionStopWatch.endCurrentTask();
response.setType(BundleType.TRANSACTIONRESPONSE);
ourLog.info("Transaction timing:\n{}", transactionStopWatch.formatTaskDurations());
return response;
}
private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set<IdType> theAllIds,
Map<IdType, IdType> theIdSubstitutions, Map<IdType, DaoMethodOutcome> theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap<BundleEntryComponent, Integer> theOriginalRequestOrder, List<BundleEntryComponent> theEntries) {
private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, String theActionName, Date theUpdateTime, Set<IdType> theAllIds,
Map<IdType, IdType> theIdSubstitutions, Map<IdType, DaoMethodOutcome> theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap<BundleEntryComponent, Integer> theOriginalRequestOrder, List<BundleEntryComponent> theEntries, StopWatch theTransactionStopWatch) {
Set<String> deletedResources = new HashSet<>();
List<DeleteConflict> deleteConflicts = new ArrayList<>();
Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<>();
@ -360,10 +373,11 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue();
String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null;
BundleEntryComponent nextRespEntry = theResponse.getEntry().get(theOriginalRequestOrder.get(nextReqEntry));
theTransactionStopWatch.startTask("Bundle.entry[" + i + "]: " + verb.name() + " " + defaultString(resourceType));
switch (verb) {
case POST: {
// CREATE
@ -466,6 +480,8 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
break;
}
theTransactionStopWatch.endCurrentTask();
}
/*
@ -484,6 +500,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
*/
FhirTerser terser = getContext().newTerser();
theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources");
for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) {
IBaseResource nextResource = nextOutcome.getResource();
if (nextResource == null) {
@ -534,8 +551,16 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
}
theTransactionStopWatch.endCurrentTask();
theTransactionStopWatch.startTask("Flush writes to database");
flushJpaSession();
theTransactionStopWatch.endCurrentTask();
if (conditionalRequestUrls.size() > 0) {
theTransactionStopWatch.startTask("Check for conflicts in conditional resources");
}
/*
* Double check we didn't allow any duplicates we shouldn't have
*/
@ -552,6 +577,8 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
}
theTransactionStopWatch.endCurrentTask();
for (IdType next : theAllIds) {
IdType replacement = theIdSubstitutions.get(next);
if (replacement == null) {

View File

@ -85,7 +85,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
@IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) org.hl7.fhir.r4.model.BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
return JpaSystemProviderDstu2.toExpungeResponse(retVal);
@ -97,7 +97,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) org.hl7.fhir.r4.model.BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
return JpaSystemProviderDstu2.toExpungeResponse(retVal);

View File

@ -66,7 +66,7 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
@IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanDt theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) BooleanDt theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanDt theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanDt theExpungeEverything
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything);
@ -79,7 +79,7 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanDt theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) BooleanDt theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanDt theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanDt theExpungeEverything
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything);

View File

@ -89,7 +89,7 @@ public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaRes
@IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) org.hl7.fhir.r4.model.BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
try {
@ -105,7 +105,7 @@ public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaRes
public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) org.hl7.fhir.r4.model.BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
try {

View File

@ -62,7 +62,7 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
@IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) BooleanType theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything);
@ -79,7 +79,7 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) BooleanType theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything);

View File

@ -84,7 +84,7 @@ public class JpaResourceProviderR4<T extends IAnyResource> extends BaseJpaResour
@IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions
) {
return super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
}
@ -95,7 +95,7 @@ public class JpaResourceProviderR4<T extends IAnyResource> extends BaseJpaResour
public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions
) {
return super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
}

View File

@ -59,7 +59,7 @@ public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus<Bundle,
@IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) BooleanType theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything
) {
return super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything);
@ -71,7 +71,7 @@ public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus<Bundle,
public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) BooleanType theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything
) {
return super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything);

View File

@ -83,7 +83,7 @@ public class JpaConstants {
/**
* Parameter name for the $expunge operation
*/
public static final String OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS = "expungeOldVersions";
public static final String OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS = "expungePreviousVersions";
/**
* Parameter name for the $expunge operation
*/

View File

@ -121,7 +121,7 @@ public class ResourceProviderExpungeR4Test extends BaseResourceProviderR4Test {
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES)
.setValue(new BooleanType(true));
input.addParameter()
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS)
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS)
.setValue(new BooleanType(true));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
@ -193,7 +193,7 @@ public class ResourceProviderExpungeR4Test extends BaseResourceProviderR4Test {
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES)
.setValue(new BooleanType(true));
input.addParameter()
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS)
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS)
.setValue(new BooleanType(true));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
@ -239,7 +239,7 @@ public class ResourceProviderExpungeR4Test extends BaseResourceProviderR4Test {
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES)
.setValue(new BooleanType(true));
input.addParameter()
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS)
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS)
.setValue(new BooleanType(true));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));

View File

@ -51,7 +51,7 @@ abstract class BaseRule implements IAuthRule {
public void addTesters(List<IAuthRuleTester> theTesters) {
theTesters.forEach(this::addTester);
}
boolean applyTesters(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource, IBaseResource theOutputResource) {
boolean retVal = true;
if (theOutputResource == null) {

View File

@ -38,7 +38,7 @@ public interface IAuthRuleBuilderOperationNamed {
/**
* Rule applies to invocations of this operation at the <code>type</code> level on any type
*/
IAuthRuleFinished onAnyType();
IAuthRuleBuilderRuleOpClassifierFinished onAnyType();
/**
* Rule applies to invocations of this operation at the <code>instance</code> level
@ -53,6 +53,11 @@ public interface IAuthRuleBuilderOperationNamed {
/**
* Rule applies to invocations of this operation at the <code>instance</code> level on any instance
*/
IAuthRuleFinished onAnyInstance();
IAuthRuleBuilderRuleOpClassifierFinished onAnyInstance();
/**
* Rule applies to invocations of this operation at any level (server, type or instance)
*/
IAuthRuleBuilderRuleOpClassifierFinished atAnyLevel();
}

View File

@ -40,11 +40,16 @@ class OperationRule extends BaseRule implements IAuthRule {
private HashSet<Class<? extends IBaseResource>> myAppliesToInstancesOfType;
private boolean myAppliesToAnyType;
private boolean myAppliesToAnyInstance;
private boolean myAppliesAtAnyLevel;
public OperationRule(String theRuleName) {
super(theRuleName);
}
public void appliesAtAnyLevel(boolean theAppliesAtAnyLevel) {
myAppliesAtAnyLevel = theAppliesAtAnyLevel;
}
public void appliesToAnyInstance() {
myAppliesToAnyInstance = true;
}
@ -82,12 +87,12 @@ class OperationRule extends BaseRule implements IAuthRule {
boolean applies = false;
switch (theOperation) {
case EXTENDED_OPERATION_SERVER:
if (myAppliesToServer) {
if (myAppliesToServer || myAppliesAtAnyLevel) {
applies = true;
}
break;
case EXTENDED_OPERATION_TYPE:
if (myAppliesToAnyType) {
if (myAppliesToAnyType || myAppliesAtAnyLevel) {
applies = true;
} else if (myAppliesToTypes != null) {
// TODO: Convert to a map of strings and keep the result
@ -101,7 +106,7 @@ class OperationRule extends BaseRule implements IAuthRule {
}
break;
case EXTENDED_OPERATION_INSTANCE:
if (myAppliesToAnyInstance) {
if (myAppliesToAnyInstance || myAppliesAtAnyLevel) {
applies = true;
} else if (theInputResourceId != null) {
if (myAppliesToIds != null) {

View File

@ -451,7 +451,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
}
@Override
public IAuthRuleFinished onAnyInstance() {
public IAuthRuleBuilderRuleOpClassifierFinished onAnyInstance() {
OperationRule rule = createRule();
rule.appliesToAnyInstance();
myRules.add(rule);
@ -459,7 +459,15 @@ public class RuleBuilder implements IAuthRuleBuilder {
}
@Override
public IAuthRuleFinished onAnyType() {
public IAuthRuleBuilderRuleOpClassifierFinished atAnyLevel() {
OperationRule rule = createRule();
rule.appliesAtAnyLevel(true);
myRules.add(rule);
return new RuleBuilderFinished(rule);
}
@Override
public IAuthRuleBuilderRuleOpClassifierFinished onAnyType() {
OperationRule rule = createRule();
rule.appliesToAnyType();
myRules.add(rule);
@ -473,7 +481,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
Validate.notBlank(theInstanceId.getIdPart(), "theInstanceId does not have an ID part");
OperationRule rule = createRule();
ArrayList<IIdType> ids = new ArrayList<IIdType>();
ArrayList<IIdType> ids = new ArrayList<>();
ids.add(theInstanceId);
rule.appliesToInstances(ids);
myRules.add(rule);
@ -509,7 +517,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
}
private HashSet<Class<? extends IBaseResource>> toTypeSet(Class<? extends IBaseResource> theType) {
HashSet<Class<? extends IBaseResource>> appliesToTypes = new HashSet<Class<? extends IBaseResource>>();
HashSet<Class<? extends IBaseResource>> appliesToTypes = new HashSet<>();
appliesToTypes.add(theType);
return appliesToTypes;
}

View File

@ -1158,6 +1158,119 @@ public class AuthorizationInterceptorR4Test {
assertFalse(ourHitMethod);
}
@Test
public void testOperationAppliesAtAnyLevel() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("RULE 1").operation().named("opName").atAnyLevel().andThen()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Server
ourHitMethod = false;
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Type
ourHitMethod = false;
ourReturn = Collections.singletonList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Instance
ourHitMethod = false;
ourReturn = Collections.singletonList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Instance Version
ourHitMethod = false;
ourReturn = Collections.singletonList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/_history/2/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@Test
public void testOperationAppliesAtAnyLevelWrongOpName() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("RULE 1").operation().named("opNameBadOp").atAnyLevel().andThen()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Server
ourHitMethod = false;
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Type
ourHitMethod = false;
ourReturn = Collections.singletonList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Instance
ourHitMethod = false;
ourReturn = Collections.singletonList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Instance Version
ourHitMethod = false;
ourReturn = Collections.singletonList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/_history/2/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testOperationTypeLevel() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {

View File

@ -71,6 +71,13 @@
source IPs, or to only allow operations with specific
parameter values.
</action>
<action type="add">
A new qualifier has been added to the AuthorizationInterceptor
RuleBuilder that allows a rule on an operation to match
<![CDATA[<code>atAnyLevel()</code>]]>, meaning that the rule
applies to the operation by name whether it is at the
server, type, or instance level.
</action>
</release>
<release version="3.3.0" date="2018-03-29">
<action type="add">