Add expunge

This commit is contained in:
James Agnew 2018-04-24 15:11:00 -04:00
parent 07e7af746f
commit 09b1f547d0
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.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -48,9 +49,8 @@ public class StopWatch {
private static final NumberFormat TEN_DAY_FORMAT = new DecimalFormat("0"); private static final NumberFormat TEN_DAY_FORMAT = new DecimalFormat("0");
private static Long ourNowForUnitTest; private static Long ourNowForUnitTest;
private long myStarted = now(); private long myStarted = now();
private long myCurrentTaskStarted = -1L; private TaskTiming myCurrentTask;
private LinkedHashMap<String, Long> myTaskTotals; private LinkedList<TaskTiming> myTasks;
private String myCurrentTaskName;
/** /**
* Constructor * Constructor
@ -68,9 +68,9 @@ public class StopWatch {
myStarted = theStart.getTime(); myStarted = theStart.getTime();
} }
private void ensureTaskTotalsMapExists() { private void addNewlineIfContentExists(StringBuilder theB) {
if (myTaskTotals == null) { if (theB.length() > 0) {
myTaskTotals = new LinkedHashMap<>(); theB.append("\n");
} }
} }
@ -80,14 +80,17 @@ public class StopWatch {
* is currently started so it's ok to call it more than once. * is currently started so it's ok to call it more than once.
*/ */
public void endCurrentTask() { public void endCurrentTask() {
if (isNotBlank(myCurrentTaskName)) { ensureTasksListExists();
ensureTaskTotalsMapExists(); if (myCurrentTask != null) {
Long existingTotal = myTaskTotals.get(myCurrentTaskName); myCurrentTask.setEnd(now());
long taskTimeElapsed = now() - myCurrentTaskStarted; }
Long newTotal = existingTotal != null ? existingTotal + taskTimeElapsed : taskTimeElapsed; myCurrentTask = null;
myTaskTotals.put(myCurrentTaskName, newTotal); }
private void ensureTasksListExists() {
if (myTasks == null) {
myTasks = new LinkedList<>();
} }
myCurrentTaskName = null;
} }
/** /**
@ -95,23 +98,49 @@ public class StopWatch {
*/ */
public String formatTaskDurations() { public String formatTaskDurations() {
// Flush the current task if it's ongoing ensureTasksListExists();
String continueTask = myCurrentTaskName;
if (isNotBlank(myCurrentTaskName)) {
endCurrentTask();
startTask(continueTask);
}
ensureTaskTotalsMapExists();
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
for (String nextTask : myTaskTotals.keySet()) {
if (b.length() > 0) { if (myTasks.size() > 0) {
b.append("\n"); long delta = myTasks.getFirst().getStart() - myStarted;
if (delta > 10) {
addNewlineIfContentExists(b);
b.append("Before first task");
b.append(": ");
b.append(formatMillis(delta));
}
} }
b.append(nextTask); 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(": ");
b.append(formatMillis(myTaskTotals.get(nextTask))); b.append(formatMillis(delta));
}
}
addNewlineIfContentExists(b);
b.append(nextTask.getTaskName());
b.append(": ");
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(); return b.toString();
@ -214,9 +243,11 @@ public class StopWatch {
public void startTask(String theTaskName) { public void startTask(String theTaskName) {
endCurrentTask(); endCurrentTask();
if (isNotBlank(theTaskName)) { 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; 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); StopWatch.setNowForUnitTestForUnitTest(1600L);
String taskDurations = sw.formatTaskDurations(); String taskDurations = sw.formatTaskDurations();
ourLog.info(taskDurations);
assertEquals("TASK1: 500ms\nTASK2: 100ms", 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()); ourLog.debug("Beginning {} with {} resources", theActionName, theRequest.getEntry().size());
final Date updateTime = new Date(); final Date updateTime = new Date();
final StopWatch transactionStopWatch = new StopWatch();
final Set<IdType> allIds = new LinkedHashSet<>(); final Set<IdType> allIds = new LinkedHashSet<>();
final Map<IdType, IdType> idSubstitutions = new HashMap<>(); 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>>() { Map<BundleEntryComponent, ResourceTable> entriesToProcess = txManager.execute(new TransactionCallback<Map<BundleEntryComponent, ResourceTable>>() {
@Override @Override
public Map<BundleEntryComponent, ResourceTable> doInTransaction(TransactionStatus status) { 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()) { for (Entry<BundleEntryComponent, ResourceTable> nextEntry : entriesToProcess.entrySet()) {
String responseLocation = nextEntry.getValue().getIdDt().toUnqualified().getValue(); String responseLocation = nextEntry.getValue().getIdDt().toUnqualified().getValue();
String responseEtag = nextEntry.getValue().getIdDt().getVersionIdPart(); 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 * 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) { for (BundleEntryComponent nextReqEntry : getEntries) {
Integer originalOrder = originalRequestOrder.get(nextReqEntry); Integer originalOrder = originalRequestOrder.get(nextReqEntry);
BundleEntryComponent nextRespEntry = response.getEntry().get(originalOrder); BundleEntryComponent nextRespEntry = response.getEntry().get(originalOrder);
@ -247,7 +256,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
int qIndex = url.indexOf('?'); int qIndex = url.indexOf('?');
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create(); ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
requestDetails.setParameters(new HashMap<String, String[]>()); requestDetails.setParameters(new HashMap<>());
if (qIndex != -1) { if (qIndex != -1) {
String params = url.substring(qIndex); String params = url.substring(qIndex);
List<NameValuePair> parameters = translateMatchUrl(params); List<NameValuePair> parameters = translateMatchUrl(params);
@ -297,13 +306,17 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
} }
} }
transactionStopWatch.endCurrentTask();
response.setType(BundleType.TRANSACTIONRESPONSE); response.setType(BundleType.TRANSACTIONRESPONSE);
ourLog.info("Transaction timing:\n{}", transactionStopWatch.formatTaskDurations());
return response; return response;
} }
private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set<IdType> theAllIds, 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) { Map<IdType, IdType> theIdSubstitutions, Map<IdType, DaoMethodOutcome> theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap<BundleEntryComponent, Integer> theOriginalRequestOrder, List<BundleEntryComponent> theEntries, StopWatch theTransactionStopWatch) {
Set<String> deletedResources = new HashSet<>(); Set<String> deletedResources = new HashSet<>();
List<DeleteConflict> deleteConflicts = new ArrayList<>(); List<DeleteConflict> deleteConflicts = new ArrayList<>();
Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<>(); Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<>();
@ -360,10 +373,11 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
} }
HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue(); HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue();
String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null; String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null;
BundleEntryComponent nextRespEntry = theResponse.getEntry().get(theOriginalRequestOrder.get(nextReqEntry)); BundleEntryComponent nextRespEntry = theResponse.getEntry().get(theOriginalRequestOrder.get(nextReqEntry));
theTransactionStopWatch.startTask("Bundle.entry[" + i + "]: " + verb.name() + " " + defaultString(resourceType));
switch (verb) { switch (verb) {
case POST: { case POST: {
// CREATE // CREATE
@ -466,6 +480,8 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
break; break;
} }
theTransactionStopWatch.endCurrentTask();
} }
/* /*
@ -484,6 +500,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
*/ */
FhirTerser terser = getContext().newTerser(); FhirTerser terser = getContext().newTerser();
theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources");
for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) { for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) {
IBaseResource nextResource = nextOutcome.getResource(); IBaseResource nextResource = nextOutcome.getResource();
if (nextResource == null) { if (nextResource == null) {
@ -534,8 +551,16 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
} }
} }
theTransactionStopWatch.endCurrentTask();
theTransactionStopWatch.startTask("Flush writes to database");
flushJpaSession(); 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 * 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) { for (IdType next : theAllIds) {
IdType replacement = theIdSubstitutions.get(next); IdType replacement = theIdSubstitutions.get(next);
if (replacement == null) { if (replacement == null) {

View File

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

View File

@ -66,7 +66,7 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
@IdParam IIdType theIdParam, @IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit, @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_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 @OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanDt theExpungeEverything
) { ) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, 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( public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit, @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_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 @OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanDt theExpungeEverything
) { ) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, 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, @IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit, @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_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); org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
try { try {
@ -105,7 +105,7 @@ public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaRes
public Parameters expunge( public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit, @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_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); org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
try { try {

View File

@ -62,7 +62,7 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
@IdParam IIdType theIdParam, @IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit, @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_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 @OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything
) { ) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, 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( public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit, @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_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 @OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything
) { ) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, 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, @IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit, @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_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); return super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
} }
@ -95,7 +95,7 @@ public class JpaResourceProviderR4<T extends IAnyResource> extends BaseJpaResour
public Parameters expunge( public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit, @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_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); return super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
} }

View File

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

View File

@ -83,7 +83,7 @@ public class JpaConstants {
/** /**
* Parameter name for the $expunge operation * 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 * 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) .setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES)
.setValue(new BooleanType(true)); .setValue(new BooleanType(true));
input.addParameter() input.addParameter()
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) .setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS)
.setValue(new BooleanType(true)); .setValue(new BooleanType(true));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input)); 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) .setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES)
.setValue(new BooleanType(true)); .setValue(new BooleanType(true));
input.addParameter() input.addParameter()
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) .setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS)
.setValue(new BooleanType(true)); .setValue(new BooleanType(true));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input)); 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) .setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES)
.setValue(new BooleanType(true)); .setValue(new BooleanType(true));
input.addParameter() input.addParameter()
.setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_OLD_VERSIONS) .setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS)
.setValue(new BooleanType(true)); .setValue(new BooleanType(true));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input)); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));

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

View File

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

View File

@ -1158,6 +1158,119 @@ public class AuthorizationInterceptorR4Test {
assertFalse(ourHitMethod); 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 @Test
public void testOperationTypeLevel() throws Exception { public void testOperationTypeLevel() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {

View File

@ -71,6 +71,13 @@
source IPs, or to only allow operations with specific source IPs, or to only allow operations with specific
parameter values. parameter values.
</action> </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>
<release version="3.3.0" date="2018-03-29"> <release version="3.3.0" date="2018-03-29">
<action type="add"> <action type="add">