Merge remote-tracking branch 'origin/master' into documentOperation
This commit is contained in:
commit
b76ad6870f
|
@ -185,14 +185,29 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
|
|||
});
|
||||
mySearchParams = Collections.unmodifiableList(searchParams);
|
||||
|
||||
Map<String, List<RuntimeSearchParam>> compartmentNameToSearchParams = new HashMap<String, List<RuntimeSearchParam>>();
|
||||
Map<String, List<RuntimeSearchParam>> compartmentNameToSearchParams = new HashMap<>();
|
||||
for (RuntimeSearchParam next : searchParams) {
|
||||
if (next.getProvidesMembershipInCompartments() != null) {
|
||||
for (String nextCompartment : next.getProvidesMembershipInCompartments()) {
|
||||
if (!compartmentNameToSearchParams.containsKey(nextCompartment)) {
|
||||
compartmentNameToSearchParams.put(nextCompartment, new ArrayList<RuntimeSearchParam>());
|
||||
compartmentNameToSearchParams.put(nextCompartment, new ArrayList<>());
|
||||
}
|
||||
List<RuntimeSearchParam> searchParamsForCompartment = compartmentNameToSearchParams.get(nextCompartment);
|
||||
searchParamsForCompartment.add(next);
|
||||
|
||||
/*
|
||||
* If one search parameter marks an SP as making a resource
|
||||
* a part of a compartment, let's also denote all other
|
||||
* SPs with the same path the same way. This behaviour is
|
||||
* used by AuthorizationInterceptor
|
||||
*/
|
||||
for (RuntimeSearchParam nextAlternate : searchParams) {
|
||||
if (nextAlternate.getPath().equals(next.getPath())) {
|
||||
if (!nextAlternate.getName().equals(next.getName())) {
|
||||
searchParamsForCompartment.add(nextAlternate);
|
||||
}
|
||||
}
|
||||
}
|
||||
compartmentNameToSearchParams.get(nextCompartment).add(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@ import static org.apache.commons.lang3.StringUtils.trim;
|
|||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
|
@ -38,6 +42,18 @@ public class RuntimeSearchParam {
|
|||
private final RestSearchParameterTypeEnum myParamType;
|
||||
private final String myPath;
|
||||
private final Set<String> myTargets;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("base", myBase)
|
||||
.append("name", myName)
|
||||
.append("path", myPath)
|
||||
.append("id", myId)
|
||||
.append("uri", myUri)
|
||||
.toString();
|
||||
}
|
||||
|
||||
private final Set<String> myProvidesMembershipInCompartments;
|
||||
private final RuntimeSearchParamStatusEnum myStatus;
|
||||
private final String myUri;
|
||||
|
@ -55,9 +71,36 @@ public class RuntimeSearchParam {
|
|||
this(theId, theUri, theName, theDescription, thePath, theParamType, theCompositeOf, theProvidesMembershipInCompartments, theTargets, theStatus, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object theO) {
|
||||
if (this == theO) return true;
|
||||
|
||||
if (theO == null || getClass() != theO.getClass()) return false;
|
||||
|
||||
RuntimeSearchParam that = (RuntimeSearchParam) theO;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(getId(), that.getId())
|
||||
.append(getName(), that.getName())
|
||||
.append(getPath(), that.getPath())
|
||||
.append(getUri(), that.getUri())
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(getId())
|
||||
.append(getName())
|
||||
.append(getPath())
|
||||
.append(getUri())
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List<RuntimeSearchParam> theCompositeOf,
|
||||
Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, Collection<String> theBase) {
|
||||
Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, Collection<String> theBase) {
|
||||
super();
|
||||
|
||||
myId = theId;
|
||||
myUri = theUri;
|
||||
myName = theName;
|
||||
|
|
|
@ -23,23 +23,23 @@ package ca.uhn.fhir.cli;
|
|||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
import org.apache.commons.csv.QuoteMode;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_30_40;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.ConceptMap;
|
||||
import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
|
||||
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
|
||||
|
@ -72,11 +72,11 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void process() throws ParseException {
|
||||
protected void process() throws ExecutionException {
|
||||
searchForConceptMapByUrl();
|
||||
}
|
||||
|
||||
private void searchForConceptMapByUrl() {
|
||||
private void searchForConceptMapByUrl() throws ExecutionException {
|
||||
ourLog.info("Searching for ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
|
||||
if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
org.hl7.fhir.dstu3.model.Bundle response = client
|
||||
|
@ -111,61 +111,26 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept
|
|||
}
|
||||
}
|
||||
|
||||
private void convertConceptMapToCsv(org.hl7.fhir.dstu3.model.ConceptMap theConceptMap) {
|
||||
ourLog.info("Exporting ConceptMap to CSV...");
|
||||
BufferedWriter bufferedWriter = null;
|
||||
CSVPrinter csvPrinter = null;
|
||||
private void convertConceptMapToCsv(org.hl7.fhir.dstu3.model.ConceptMap theConceptMap) throws ExecutionException {
|
||||
try {
|
||||
bufferedWriter = Files.newBufferedWriter(Paths.get(file));
|
||||
csvPrinter = new CSVPrinter(
|
||||
bufferedWriter,
|
||||
CSVFormat
|
||||
.DEFAULT
|
||||
.withRecordSeparator("\n")
|
||||
.withHeader(Header.class));
|
||||
|
||||
for (org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent group : theConceptMap.getGroup()) {
|
||||
for (org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent element : group.getElement()) {
|
||||
for (org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent target : element.getTarget()) {
|
||||
|
||||
List<String> columns = new ArrayList<>();
|
||||
columns.add(defaultString(group.getSource()));
|
||||
columns.add(defaultString(group.getSourceVersion()));
|
||||
columns.add(defaultString(group.getTarget()));
|
||||
columns.add(defaultString(group.getTargetVersion()));
|
||||
columns.add(defaultString(element.getCode()));
|
||||
columns.add(defaultString(element.getDisplay()));
|
||||
columns.add(defaultString(target.getCode()));
|
||||
columns.add(defaultString(target.getDisplay()));
|
||||
columns.add(defaultString(target.getEquivalence().toCode()));
|
||||
columns.add(defaultString(target.getComment()));
|
||||
|
||||
csvPrinter.print(columns);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new InternalErrorException(ioe);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(csvPrinter);
|
||||
IOUtils.closeQuietly(bufferedWriter);
|
||||
convertConceptMapToCsv(VersionConvertor_30_40.convertConceptMap(theConceptMap));
|
||||
} catch (FHIRException fe) {
|
||||
throw new ExecutionException(fe);
|
||||
}
|
||||
ourLog.info("Finished exporting to {}", file);
|
||||
}
|
||||
|
||||
private void convertConceptMapToCsv(ConceptMap theConceptMap) {
|
||||
ourLog.info("Exporting ConceptMap to CSV...");
|
||||
Writer writer = null;
|
||||
CSVPrinter csvPrinter = null;
|
||||
try {
|
||||
writer = Files.newBufferedWriter(Paths.get(file));
|
||||
csvPrinter = new CSVPrinter(
|
||||
try (
|
||||
Writer writer = Files.newBufferedWriter(Paths.get(file));
|
||||
CSVPrinter csvPrinter = new CSVPrinter(
|
||||
writer,
|
||||
CSVFormat
|
||||
.DEFAULT
|
||||
.withRecordSeparator("\n")
|
||||
.withHeader(Header.class).withQuoteMode(QuoteMode.ALL));
|
||||
|
||||
.withHeader(Header.class)
|
||||
.withQuoteMode(QuoteMode.ALL));
|
||||
) {
|
||||
for (ConceptMapGroupComponent group : theConceptMap.getGroup()) {
|
||||
for (SourceElementComponent element : group.getElement()) {
|
||||
for (ConceptMap.TargetElementComponent target : element.getTarget()) {
|
||||
|
@ -188,10 +153,8 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept
|
|||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new InternalErrorException(ioe);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(csvPrinter);
|
||||
IOUtils.closeQuietly(writer);
|
||||
}
|
||||
|
||||
ourLog.info("Finished exporting to {}", file);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_30_40;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4.model.ConceptMap;
|
||||
|
@ -43,7 +41,10 @@ import java.io.IOException;
|
|||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
@ -96,7 +97,7 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void parseAdditionalParameters(CommandLine theCommandLine) throws ParseException {
|
||||
protected void parseAdditionalParameters(CommandLine theCommandLine) {
|
||||
sourceValueSet = theCommandLine.getOptionValue(SOURCE_VALUE_SET_PARAM);
|
||||
if (isBlank(sourceValueSet)) {
|
||||
ourLog.info("Source value set is not specified (i.e. ConceptMap.sourceUri).");
|
||||
|
@ -113,11 +114,11 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void process() throws ParseException, ExecutionException {
|
||||
protected void process() throws ExecutionException {
|
||||
searchForConceptMapByUrl();
|
||||
}
|
||||
|
||||
private void searchForConceptMapByUrl() throws ParseException, ExecutionException {
|
||||
private void searchForConceptMapByUrl() throws ExecutionException {
|
||||
if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
org.hl7.fhir.dstu3.model.ConceptMap conceptMap = convertCsvToConceptMapDstu3();
|
||||
|
||||
|
@ -153,7 +154,7 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
|
|||
}
|
||||
}
|
||||
|
||||
private org.hl7.fhir.dstu3.model.ConceptMap convertCsvToConceptMapDstu3() throws ParseException, ExecutionException {
|
||||
private org.hl7.fhir.dstu3.model.ConceptMap convertCsvToConceptMapDstu3() throws ExecutionException {
|
||||
try {
|
||||
return VersionConvertor_30_40.convertConceptMap(convertCsvToConceptMapR4());
|
||||
} catch (FHIRException fe) {
|
||||
|
@ -161,14 +162,12 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
|
|||
}
|
||||
}
|
||||
|
||||
private ConceptMap convertCsvToConceptMapR4() throws ParseException, ExecutionException {
|
||||
private ConceptMap convertCsvToConceptMapR4() throws ExecutionException {
|
||||
ourLog.info("Converting CSV to ConceptMap...");
|
||||
ConceptMap retVal = new ConceptMap();
|
||||
Reader reader = null;
|
||||
CSVParser csvParser = null;
|
||||
try {
|
||||
reader = Files.newBufferedReader(Paths.get(file));
|
||||
csvParser = new CSVParser(
|
||||
try (
|
||||
Reader reader = Files.newBufferedReader(Paths.get(file));
|
||||
CSVParser csvParser = new CSVParser(
|
||||
reader,
|
||||
CSVFormat
|
||||
.DEFAULT
|
||||
|
@ -178,7 +177,7 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
|
|||
.withIgnoreHeaderCase()
|
||||
.withIgnoreEmptyLines()
|
||||
.withTrim());
|
||||
|
||||
) {
|
||||
retVal.setUrl(conceptMapUrl);
|
||||
|
||||
if (isNotBlank(sourceValueSet)) {
|
||||
|
@ -278,9 +277,6 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
|
|||
}
|
||||
} catch (IOException e) {
|
||||
throw new InternalErrorException(e);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(csvParser);
|
||||
IOUtils.closeQuietly(reader);
|
||||
}
|
||||
|
||||
ourLog.info("Finished converting CSV to ConceptMap.");
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
package ca.uhn.fhir.cli;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.dstu3.model.ConceptMap;
|
||||
import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence;
|
||||
import org.hl7.fhir.dstu3.model.UriType;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ExportConceptMapToCsvCommandDstu3Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandDstu3Test.class);
|
||||
private static final String CM_URL = "http://example.com/conceptmap";
|
||||
private static final String VS_URL_1 = "http://example.com/valueset/1";
|
||||
private static final String VS_URL_2 = "http://example.com/valueset/2";
|
||||
private static final String CS_URL_1 = "http://example.com/codesystem/1";
|
||||
private static final String CS_URL_2 = "http://example.com/codesystem/2";
|
||||
private static final String CS_URL_3 = "http://example.com/codesystem/3";
|
||||
private static final String FILE = "./target/output.csv";
|
||||
|
||||
private static String ourBase;
|
||||
private static IGenericClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static String ourVersion = "dstu3";
|
||||
|
||||
static {
|
||||
System.setProperty("test", "true");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
ServletHandler servletHandler = new ServletHandler();
|
||||
|
||||
RestfulServer restfulServer = new RestfulServer(ourCtx);
|
||||
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
|
||||
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapDstu3(ourCtx));
|
||||
|
||||
ServletHolder servletHolder = new ServletHolder(restfulServer);
|
||||
servletHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(servletHandler);
|
||||
|
||||
ourServer.start();
|
||||
|
||||
ourBase = "http://localhost:" + ourPort;
|
||||
|
||||
ourClient = ourCtx.newRestfulGenericClient(ourBase);
|
||||
|
||||
ourClient.create().resource(createConceptMap()).execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportConceptMapToCsvCommand() throws IOException {
|
||||
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[] {"export-conceptmap-to-csv",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
||||
"-l"});
|
||||
|
||||
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
|
||||
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
|
||||
assertEquals(expected, result);
|
||||
|
||||
FileUtils.deleteQuietly(new File(FILE));
|
||||
}
|
||||
|
||||
static ConceptMap createConceptMap() {
|
||||
ConceptMap conceptMap = new ConceptMap();
|
||||
conceptMap
|
||||
.setUrl(CM_URL)
|
||||
.setSource(new UriType(VS_URL_1))
|
||||
.setTarget(new UriType(VS_URL_2));
|
||||
|
||||
ConceptMap.ConceptMapGroupComponent group = conceptMap.addGroup();
|
||||
group
|
||||
.setSource(CS_URL_1)
|
||||
.setSourceVersion("Version 1s")
|
||||
.setTarget(CS_URL_2)
|
||||
.setTargetVersion("Version 2t");
|
||||
|
||||
ConceptMap.SourceElementComponent element = group.addElement();
|
||||
element
|
||||
.setCode("Code 1a")
|
||||
.setDisplay("Display 1a");
|
||||
|
||||
ConceptMap.TargetElementComponent target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 2a")
|
||||
.setDisplay("Display 2a")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("2a This is a comment.");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 1b")
|
||||
.setDisplay("Display 1b");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 2b")
|
||||
.setDisplay("Display 2b")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("2b This is a comment.");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 1c")
|
||||
.setDisplay("Display 1c");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 2c")
|
||||
.setDisplay("Display 2c")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("2c This is a comment.");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 1d")
|
||||
.setDisplay("Display 1d");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 2d")
|
||||
.setDisplay("Display 2d")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("2d This is a comment.");
|
||||
|
||||
group = conceptMap.addGroup();
|
||||
group
|
||||
.setSource(CS_URL_1)
|
||||
.setSourceVersion("Version 1s")
|
||||
.setTarget(CS_URL_3)
|
||||
.setTargetVersion("Version 3t");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 1a")
|
||||
.setDisplay("Display 1a");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 3a")
|
||||
.setDisplay("Display 3a")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("3a This is a comment.");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 1b")
|
||||
.setDisplay("Display 1b");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 3b")
|
||||
.setDisplay("Display 3b")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("3b This is a comment.");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 1c")
|
||||
.setDisplay("Display 1c");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 3c")
|
||||
.setDisplay("Display 3c")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("3c This is a comment.");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 1d")
|
||||
.setDisplay("Display 1d");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 3d")
|
||||
.setDisplay("Display 3d")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("3d This is a comment.");
|
||||
|
||||
group = conceptMap.addGroup();
|
||||
group
|
||||
.setSource(CS_URL_2)
|
||||
.setSourceVersion("Version 2s")
|
||||
.setTarget(CS_URL_3)
|
||||
.setTargetVersion("Version 3t");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 2a")
|
||||
.setDisplay("Display 2a");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 3a")
|
||||
.setDisplay("Display 3a")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("3a This is a comment.");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 2b")
|
||||
.setDisplay("Display 2b");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 3b")
|
||||
.setDisplay("Display 3b")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("3b This is a comment.");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 2c")
|
||||
.setDisplay("Display 2c");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 3c")
|
||||
.setDisplay("Display 3c")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("3c This is a comment.");
|
||||
|
||||
element = group.addElement();
|
||||
element
|
||||
.setCode("Code 2d")
|
||||
.setDisplay("Display 2d");
|
||||
|
||||
target = element.addTarget();
|
||||
target
|
||||
.setCode("Code 3d")
|
||||
.setDisplay("Display 3d")
|
||||
.setEquivalence(ConceptMapEquivalence.EQUAL)
|
||||
.setComment("3d This is a comment.");
|
||||
|
||||
return conceptMap;
|
||||
}
|
||||
}
|
|
@ -25,8 +25,8 @@ import java.io.IOException;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ExportConceptMapToCsvCommandTest {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandTest.class);
|
||||
public class ExportConceptMapToCsvCommandR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandR4Test.class);
|
||||
private static final String CM_URL = "http://example.com/conceptmap";
|
||||
private static final String VS_URL_1 = "http://example.com/valueset/1";
|
||||
private static final String VS_URL_2 = "http://example.com/valueset/2";
|
||||
|
@ -40,6 +40,7 @@ public class ExportConceptMapToCsvCommandTest {
|
|||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static String ourVersion = "r4";
|
||||
|
||||
static {
|
||||
System.setProperty("test", "true");
|
||||
|
@ -80,7 +81,7 @@ public class ExportConceptMapToCsvCommandTest {
|
|||
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[] {"export-conceptmap-to-csv",
|
||||
"-v", "r4",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
|
@ -0,0 +1,127 @@
|
|||
package ca.uhn.fhir.cli;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Server Framework
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 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.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.provider.AbstractHashMapResourceProvider;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
import org.hl7.fhir.dstu3.model.ConceptMap;
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/**
|
||||
* This is a subclass to implement FHIR operations specific to DSTU3 ConceptMap
|
||||
* resources. Its superclass, {@link AbstractHashMapResourceProvider}, is a simple
|
||||
* implementation of the resource provider interface that uses a HashMap to
|
||||
* store all resources in memory.
|
||||
* <p>
|
||||
* This subclass currently supports the following FHIR operations:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Search for DSTU3 ConceptMap resources by ConceptMap.url</li>
|
||||
* <li>Conditional update for DSTU3 ConceptMap resources by ConceptMap.url</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class HashMapResourceProviderConceptMapDstu3 extends AbstractHashMapResourceProvider<ConceptMap> {
|
||||
@SuppressWarnings("unchecked")
|
||||
public HashMapResourceProviderConceptMapDstu3(FhirContext theFhirContext) {
|
||||
super(theFhirContext, ConceptMap.class);
|
||||
|
||||
FhirVersionEnum fhirVersion = theFhirContext.getVersion().getVersion();
|
||||
if (fhirVersion != FhirVersionEnum.DSTU3) {
|
||||
throw new IllegalStateException("Requires FHIR version DSTU3. Unsupported FHIR version provided: " + fhirVersion);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Search
|
||||
public List<ConceptMap> searchByUrl(
|
||||
@RequiredParam(name=ConceptMap.SP_URL) String theConceptMapUrl) {
|
||||
|
||||
List<ConceptMap> retVal = new ArrayList<>();
|
||||
|
||||
for (TreeMap<Long, ConceptMap> next : myIdToVersionToResourceMap.values()) {
|
||||
if (!next.isEmpty()) {
|
||||
ConceptMap conceptMap = next.lastEntry().getValue();
|
||||
if (theConceptMapUrl.equals(conceptMap.getUrl()))
|
||||
retVal.add(conceptMap);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Update
|
||||
public MethodOutcome updateConceptMapConditional(
|
||||
@ResourceParam ConceptMap theConceptMap,
|
||||
@IdParam IdType theId,
|
||||
@ConditionalUrlParam String theConditional) {
|
||||
|
||||
MethodOutcome methodOutcome = new MethodOutcome();
|
||||
|
||||
if (theConditional != null) {
|
||||
|
||||
String url = null;
|
||||
|
||||
try {
|
||||
List<NameValuePair> params = URLEncodedUtils.parse(new URI(theConditional), Charsets.UTF_8);
|
||||
for (NameValuePair param : params) {
|
||||
if (param.getName().equalsIgnoreCase("url")) {
|
||||
url = param.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (URISyntaxException urise) {
|
||||
throw new InvalidRequestException(urise);
|
||||
}
|
||||
|
||||
if (isNotBlank(url)) {
|
||||
List<ConceptMap> conceptMaps = searchByUrl(url);
|
||||
|
||||
if (!conceptMaps.isEmpty()) {
|
||||
methodOutcome = update(conceptMaps.get(0));
|
||||
} else {
|
||||
methodOutcome = create(theConceptMap);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
methodOutcome = update(theConceptMap);
|
||||
}
|
||||
|
||||
return methodOutcome;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,367 @@
|
|||
package ca.uhn.fhir.cli;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.ConceptMap;
|
||||
import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent;
|
||||
import org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent;
|
||||
import org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent;
|
||||
import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ImportCsvToConceptMapCommandDstu3Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommandDstu3Test.class);
|
||||
private static final String CM_URL = "http://example.com/conceptmap";
|
||||
private static final String VS_URL_1 = "http://example.com/valueset/1";
|
||||
private static final String VS_URL_2 = "http://example.com/valueset/2";
|
||||
private static final String CS_URL_1 = "http://example.com/codesystem/1";
|
||||
private static final String CS_URL_2 = "http://example.com/codesystem/2";
|
||||
private static final String CS_URL_3 = "http://example.com/codesystem/3";
|
||||
private static final String FILENAME = "import-csv-to-conceptmap-command-test-input.csv";
|
||||
|
||||
private static String file;
|
||||
private static String ourBase;
|
||||
private static IGenericClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static String ourVersion = "dstu3";
|
||||
|
||||
private static RestfulServer restfulServer;
|
||||
|
||||
private static HashMapResourceProviderConceptMapDstu3 hashMapResourceProviderConceptMapDstu3;
|
||||
|
||||
static {
|
||||
System.setProperty("test", "true");
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterClearResourceProvider() {
|
||||
HashMapResourceProviderConceptMapDstu3 resourceProvider = (HashMapResourceProviderConceptMapDstu3) restfulServer.getResourceProviders().iterator().next();
|
||||
resourceProvider.clear();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
ServletHandler servletHandler = new ServletHandler();
|
||||
|
||||
restfulServer = new RestfulServer(ourCtx);
|
||||
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
|
||||
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapDstu3(ourCtx));
|
||||
|
||||
ServletHolder servletHolder = new ServletHolder(restfulServer);
|
||||
servletHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(servletHandler);
|
||||
|
||||
ourServer.start();
|
||||
|
||||
ourBase = "http://localhost:" + ourPort;
|
||||
|
||||
ourClient = ourCtx.newRestfulGenericClient(ourBase);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConditionalUpdateResultsInCreate() {
|
||||
ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap();
|
||||
String conceptMapUrl = conceptMap.getUrl();
|
||||
|
||||
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
|
||||
MethodOutcome methodOutcome = ourClient
|
||||
.update()
|
||||
.resource(conceptMap)
|
||||
.conditional()
|
||||
.where(ConceptMap.URL.matches().value(conceptMapUrl))
|
||||
.execute();
|
||||
|
||||
assertEquals(Boolean.TRUE, methodOutcome.getCreated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConditionalUpdateResultsInUpdate() {
|
||||
ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap();
|
||||
ourClient.create().resource(conceptMap).execute();
|
||||
String conceptMapUrl = conceptMap.getUrl();
|
||||
|
||||
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
|
||||
MethodOutcome methodOutcome = ourClient
|
||||
.update()
|
||||
.resource(conceptMap)
|
||||
.conditional()
|
||||
.where(ConceptMap.URL.matches().value(conceptMapUrl))
|
||||
.execute();
|
||||
|
||||
assertNull(methodOutcome.getCreated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonConditionalUpdate() {
|
||||
ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap();
|
||||
ourClient.create().resource(conceptMap).execute();
|
||||
|
||||
Bundle response = ourClient
|
||||
.search()
|
||||
.forResource(ConceptMap.class)
|
||||
.where(ConceptMap.URL.matches().value(CM_URL))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
ConceptMap resultConceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
|
||||
|
||||
MethodOutcome methodOutcome = ourClient
|
||||
.update()
|
||||
.resource(resultConceptMap)
|
||||
.withId(resultConceptMap.getIdElement())
|
||||
.execute();
|
||||
|
||||
assertNull(methodOutcome.getCreated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportCsvToConceptMapCommand() throws FHIRException {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
File fileToImport = new File(classLoader.getResource(FILENAME).getFile());
|
||||
ImportCsvToConceptMapCommandDstu3Test.file = fileToImport.getAbsolutePath();
|
||||
|
||||
App.main(new String[] {"import-csv-to-conceptmap",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-i", VS_URL_1,
|
||||
"-o", VS_URL_2,
|
||||
"-f", file,
|
||||
"-l"});
|
||||
|
||||
Bundle response = ourClient
|
||||
.search()
|
||||
.forResource(ConceptMap.class)
|
||||
.where(ConceptMap.URL.matches().value(CM_URL))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
|
||||
|
||||
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
|
||||
|
||||
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/1", conceptMap.getId());
|
||||
|
||||
assertEquals(CM_URL, conceptMap.getUrl());
|
||||
assertEquals(VS_URL_1, conceptMap.getSourceUriType().getValueAsString());
|
||||
assertEquals(VS_URL_2, conceptMap.getTargetUriType().getValueAsString());
|
||||
|
||||
assertEquals(3, conceptMap.getGroup().size());
|
||||
|
||||
ConceptMapGroupComponent group = conceptMap.getGroup().get(0);
|
||||
assertEquals(CS_URL_1, group.getSource());
|
||||
assertEquals("Version 1s", group.getSourceVersion());
|
||||
assertEquals(CS_URL_2, group.getTarget());
|
||||
assertEquals("Version 2t", group.getTargetVersion());
|
||||
|
||||
assertEquals(4, group.getElement().size());
|
||||
|
||||
SourceElementComponent source = group.getElement().get(0);
|
||||
assertEquals("Code 1a", source.getCode());
|
||||
assertEquals("Display 1a", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
TargetElementComponent target = source.getTarget().get(0);
|
||||
assertEquals("Code 2a", target.getCode());
|
||||
assertEquals("Display 2a", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("2a This is a comment.", target.getComment());
|
||||
|
||||
source = group.getElement().get(1);
|
||||
assertEquals("Code 1b", source.getCode());
|
||||
assertEquals("Display 1b", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 2b", target.getCode());
|
||||
assertEquals("Display 2b", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("2b This is a comment.", target.getComment());
|
||||
|
||||
source = group.getElement().get(2);
|
||||
assertEquals("Code 1c", source.getCode());
|
||||
assertEquals("Display 1c", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 2c", target.getCode());
|
||||
assertEquals("Display 2c", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("2c This is a comment.", target.getComment());
|
||||
|
||||
source = group.getElement().get(3);
|
||||
assertEquals("Code 1d", source.getCode());
|
||||
assertEquals("Display 1d", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 2d", target.getCode());
|
||||
assertEquals("Display 2d", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("2d This is a comment.", target.getComment());
|
||||
|
||||
group = conceptMap.getGroup().get(1);
|
||||
assertEquals(CS_URL_1, group.getSource());
|
||||
assertEquals("Version 1s", group.getSourceVersion());
|
||||
assertEquals(CS_URL_3, group.getTarget());
|
||||
assertEquals("Version 3t", group.getTargetVersion());
|
||||
|
||||
assertEquals(4, group.getElement().size());
|
||||
|
||||
source = group.getElement().get(0);
|
||||
assertEquals("Code 1a", source.getCode());
|
||||
assertEquals("Display 1a", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 3a", target.getCode());
|
||||
assertEquals("Display 3a", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("3a This is a comment.", target.getComment());
|
||||
|
||||
source = group.getElement().get(1);
|
||||
assertEquals("Code 1b", source.getCode());
|
||||
assertEquals("Display 1b", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 3b", target.getCode());
|
||||
assertEquals("Display 3b", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("3b This is a comment.", target.getComment());
|
||||
|
||||
source = group.getElement().get(2);
|
||||
assertEquals("Code 1c", source.getCode());
|
||||
assertEquals("Display 1c", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 3c", target.getCode());
|
||||
assertEquals("Display 3c", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("3c This is a comment.", target.getComment());
|
||||
|
||||
source = group.getElement().get(3);
|
||||
assertEquals("Code 1d", source.getCode());
|
||||
assertEquals("Display 1d", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 3d", target.getCode());
|
||||
assertEquals("Display 3d", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("3d This is a comment.", target.getComment());
|
||||
|
||||
group = conceptMap.getGroup().get(2);
|
||||
assertEquals(CS_URL_2, group.getSource());
|
||||
assertEquals("Version 2s", group.getSourceVersion());
|
||||
assertEquals(CS_URL_3, group.getTarget());
|
||||
assertEquals("Version 3t", group.getTargetVersion());
|
||||
|
||||
assertEquals(4, group.getElement().size());
|
||||
|
||||
source = group.getElement().get(0);
|
||||
assertEquals("Code 2a", source.getCode());
|
||||
assertEquals("Display 2a", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 3a", target.getCode());
|
||||
assertEquals("Display 3a", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("3a This is a comment.", target.getComment());
|
||||
|
||||
source = group.getElement().get(1);
|
||||
assertEquals("Code 2b", source.getCode());
|
||||
assertEquals("Display 2b", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 3b", target.getCode());
|
||||
assertEquals("Display 3b", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("3b This is a comment.", target.getComment());
|
||||
|
||||
source = group.getElement().get(2);
|
||||
assertEquals("Code 2c", source.getCode());
|
||||
assertEquals("Display 2c", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 3c", target.getCode());
|
||||
assertEquals("Display 3c", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("3c This is a comment.", target.getComment());
|
||||
|
||||
source = group.getElement().get(3);
|
||||
assertEquals("Code 2d", source.getCode());
|
||||
assertEquals("Display 2d", source.getDisplay());
|
||||
|
||||
assertEquals(1, source.getTarget().size());
|
||||
|
||||
target = source.getTarget().get(0);
|
||||
assertEquals("Code 3d", target.getCode());
|
||||
assertEquals("Display 3d", target.getDisplay());
|
||||
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
|
||||
assertEquals("3d This is a comment.", target.getComment());
|
||||
|
||||
App.main(new String[] {"import-csv-to-conceptmap",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-i", VS_URL_1,
|
||||
"-o", VS_URL_2,
|
||||
"-f", file,
|
||||
"-l"});
|
||||
|
||||
response = ourClient
|
||||
.search()
|
||||
.forResource(ConceptMap.class)
|
||||
.where(ConceptMap.URL.matches().value(CM_URL))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
|
||||
|
||||
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/2", conceptMap.getId());
|
||||
}
|
||||
}
|
|
@ -10,13 +10,13 @@ import ca.uhn.fhir.util.TestUtil;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.ConceptMap;
|
||||
import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
|
||||
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
|
||||
import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent;
|
||||
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
@ -26,8 +26,8 @@ import java.io.File;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ImportCsvToConceptMapCommandTest {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommandTest.class);
|
||||
public class ImportCsvToConceptMapCommandR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommandR4Test.class);
|
||||
private static final String CM_URL = "http://example.com/conceptmap";
|
||||
private static final String VS_URL_1 = "http://example.com/valueset/1";
|
||||
private static final String VS_URL_2 = "http://example.com/valueset/2";
|
||||
|
@ -42,6 +42,7 @@ public class ImportCsvToConceptMapCommandTest {
|
|||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static String ourVersion = "r4";
|
||||
|
||||
private static RestfulServer restfulServer;
|
||||
|
||||
|
@ -87,7 +88,7 @@ public class ImportCsvToConceptMapCommandTest {
|
|||
|
||||
@Test
|
||||
public void testConditionalUpdateResultsInCreate() {
|
||||
ConceptMap conceptMap = ExportConceptMapToCsvCommandTest.createConceptMap();
|
||||
ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap();
|
||||
String conceptMapUrl = conceptMap.getUrl();
|
||||
|
||||
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
|
||||
|
@ -104,7 +105,7 @@ public class ImportCsvToConceptMapCommandTest {
|
|||
|
||||
@Test
|
||||
public void testConditionalUpdateResultsInUpdate() {
|
||||
ConceptMap conceptMap = ExportConceptMapToCsvCommandTest.createConceptMap();
|
||||
ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap();
|
||||
ourClient.create().resource(conceptMap).execute();
|
||||
String conceptMapUrl = conceptMap.getUrl();
|
||||
|
||||
|
@ -122,7 +123,7 @@ public class ImportCsvToConceptMapCommandTest {
|
|||
|
||||
@Test
|
||||
public void testNonConditionalUpdate() {
|
||||
ConceptMap conceptMap = ExportConceptMapToCsvCommandTest.createConceptMap();
|
||||
ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap();
|
||||
ourClient.create().resource(conceptMap).execute();
|
||||
|
||||
Bundle response = ourClient
|
||||
|
@ -150,10 +151,10 @@ public class ImportCsvToConceptMapCommandTest {
|
|||
public void testImportCsvToConceptMapCommand() throws FHIRException {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
File fileToImport = new File(classLoader.getResource(FILENAME).getFile());
|
||||
ImportCsvToConceptMapCommandTest.file = fileToImport.getAbsolutePath();
|
||||
ImportCsvToConceptMapCommandR4Test.file = fileToImport.getAbsolutePath();
|
||||
|
||||
App.main(new String[] {"import-csv-to-conceptmap",
|
||||
"-v", "r4",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-i", VS_URL_1,
|
||||
|
@ -349,7 +350,7 @@ public class ImportCsvToConceptMapCommandTest {
|
|||
assertEquals("3d This is a comment.", target.getComment());
|
||||
|
||||
App.main(new String[] {"import-csv-to-conceptmap",
|
||||
"-v", "r4",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-i", VS_URL_1,
|
|
@ -1338,7 +1338,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
ourLog.info("Starting resource DAO for type: {}", getResourceName());
|
||||
ourLog.debug("Starting resource DAO for type: {}", getResourceName());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -257,7 +257,7 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
|
|||
@Transactional(propagation = Propagation.NEVER)
|
||||
public Integer performReindexingPass(final Integer theCount) {
|
||||
if (!myReindexLock.tryLock()) {
|
||||
return null;
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
return doPerformReindexingPass(theCount);
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.springframework.data.repository.query.Param;
|
|||
*/
|
||||
|
||||
public interface ITermConceptMapGroupDao extends JpaRepository<TermConceptMapGroup, Long> {
|
||||
@Query("DELETE FROM TermConceptMapGroup g WHERE g.myConceptMap.myId = :pid")
|
||||
@Query("DELETE FROM TermConceptMapGroup g WHERE g.myId = :pid")
|
||||
@Modifying
|
||||
void deleteTermConceptMapGroupById(@Param("pid") Long theId);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.springframework.data.repository.query.Param;
|
|||
*/
|
||||
|
||||
public interface ITermConceptMapGroupElementDao extends JpaRepository<TermConceptMapGroupElement, Long> {
|
||||
@Query("DELETE FROM TermConceptMapGroupElement e WHERE e.myConceptMapGroup.myConceptMap.myId = :pid")
|
||||
@Query("DELETE FROM TermConceptMapGroupElement e WHERE e.myId = :pid")
|
||||
@Modifying
|
||||
void deleteTermConceptMapGroupElementById(@Param("pid") Long theId);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.springframework.data.repository.query.Param;
|
|||
*/
|
||||
|
||||
public interface ITermConceptMapGroupElementTargetDao extends JpaRepository<TermConceptMapGroupElementTarget, Long> {
|
||||
@Query("DELETE FROM TermConceptMapGroupElementTarget t WHERE t.myConceptMapGroupElement.myConceptMapGroup.myConceptMap.myId = :pid")
|
||||
@Query("DELETE FROM TermConceptMapGroupElementTarget t WHERE t.myId = :pid")
|
||||
@Modifying
|
||||
void deleteTermConceptMapGroupElementTargetById(@Param("pid") Long theId);
|
||||
}
|
||||
|
|
|
@ -115,11 +115,12 @@ public class TermConceptMap implements Serializable {
|
|||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("myId", myId)
|
||||
.append("myResource", myResource.toString())
|
||||
.append(myResource != null ? ("myResource=" + myResource.toString()) : ("myResource=(null)"))
|
||||
.append("myResourcePid", myResourcePid)
|
||||
.append("mySource", mySource)
|
||||
.append("myTarget", myTarget)
|
||||
.append("myUrl", myUrl)
|
||||
.append("myConceptMapGroups - size", myConceptMapGroups.size())
|
||||
.append(myConceptMapGroups != null ? ("myConceptMapGroups - size=" + myConceptMapGroups.size()) : ("myConceptMapGroups=(null)"))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,12 +137,12 @@ public class TermConceptMapGroup implements Serializable {
|
|||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("myId", myId)
|
||||
.append("myConceptMap - id", myConceptMap.getId())
|
||||
.append(myConceptMap != null ? ("myConceptMap - id=" + myConceptMap.getId()) : ("myConceptMap=(null)"))
|
||||
.append("mySource", mySource)
|
||||
.append("mySourceVersion", mySourceVersion)
|
||||
.append("myTarget", myTarget)
|
||||
.append("myTargetVersion", myTargetVersion)
|
||||
.append("myConceptMapGroupElements - size", myConceptMapGroupElements.size())
|
||||
.append(myConceptMapGroupElements != null ? ("myConceptMapGroupElements - size=" + myConceptMapGroupElements.size()) : ("myConceptMapGroupElements=(null)"))
|
||||
.append("myConceptMapUrl", this.getConceptMapUrl())
|
||||
.append("mySourceValueSet", this.getSourceValueSet())
|
||||
.append("myTargetValueSet", this.getTargetValueSet())
|
||||
|
|
|
@ -151,10 +151,10 @@ public class TermConceptMapGroupElement implements Serializable {
|
|||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("myId", myId)
|
||||
.append("myConceptMapGroup - id", myConceptMapGroup.getId())
|
||||
.append(myConceptMapGroup != null ? ("myConceptMapGroup - id=" + myConceptMapGroup.getId()) : ("myConceptMapGroup=(null)"))
|
||||
.append("myCode", myCode)
|
||||
.append("myDisplay", myDisplay)
|
||||
.append("myConceptMapGroupElementTargets - size", myConceptMapGroupElementTargets.size())
|
||||
.append(myConceptMapGroupElementTargets != null ? ("myConceptMapGroupElementTargets - size=" + myConceptMapGroupElementTargets.size()) : ("myConceptMapGroupElementTargets=(null)"))
|
||||
.append("myConceptMapUrl", this.getConceptMapUrl())
|
||||
.append("mySystem", this.getSystem())
|
||||
.append("mySystemVersion", this.getSystemVersion())
|
||||
|
|
|
@ -153,7 +153,7 @@ public class TermConceptMapGroupElementTarget implements Serializable {
|
|||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("myId", myId)
|
||||
.append("myConceptMapGroupElement - id", myConceptMapGroupElement.getId())
|
||||
.append(myConceptMapGroupElement != null ? ("myConceptMapGroupElement - id=" + myConceptMapGroupElement.getId()) : ("myConceptMapGroupElement=(null)"))
|
||||
.append("myCode", myCode)
|
||||
.append("myDisplay", myDisplay)
|
||||
.append("myEquivalence", myEquivalence.toCode())
|
||||
|
|
|
@ -964,20 +964,33 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
|
|||
termConceptMap.setUrl(theConceptMap.getUrl());
|
||||
|
||||
// Get existing entity so it can be deleted.
|
||||
Optional<TermConceptMap> optionalExistingTermConceptMapById = myConceptMapDao.findTermConceptMapByResourcePid(termConceptMap.getResourcePid());
|
||||
Optional<TermConceptMap> optionalExistingTermConceptMapById = myConceptMapDao.findTermConceptMapByResourcePid(theResourceTable.getId());
|
||||
|
||||
/*
|
||||
* For now we always delete old versions. At some point, it would be nice to allow configuration to keep old versions.
|
||||
*/
|
||||
|
||||
if (optionalExistingTermConceptMapById.isPresent()) {
|
||||
Long id = optionalExistingTermConceptMapById.get().getId();
|
||||
ourLog.info("Deleting existing TermConceptMap {} and its children...", id);
|
||||
myConceptMapGroupElementTargetDao.deleteTermConceptMapGroupElementTargetById(id);
|
||||
myConceptMapGroupElementDao.deleteTermConceptMapGroupElementById(id);
|
||||
myConceptMapGroupDao.deleteTermConceptMapGroupById(id);
|
||||
myConceptMapDao.deleteTermConceptMapById(id);
|
||||
ourLog.info("Done deleting existing TermConceptMap {} and its children.", id);
|
||||
TermConceptMap existingTermConceptMap = optionalExistingTermConceptMapById.get();
|
||||
|
||||
ourLog.info("Deleting existing TermConceptMap {} and its children...", existingTermConceptMap.getId());
|
||||
for (TermConceptMapGroup group : existingTermConceptMap.getConceptMapGroups()) {
|
||||
|
||||
for (TermConceptMapGroupElement element : group.getConceptMapGroupElements()) {
|
||||
|
||||
for (TermConceptMapGroupElementTarget target : element.getConceptMapGroupElementTargets()) {
|
||||
|
||||
myConceptMapGroupElementTargetDao.deleteTermConceptMapGroupElementTargetById(target.getId());
|
||||
}
|
||||
|
||||
myConceptMapGroupElementDao.deleteTermConceptMapGroupElementById(element.getId());
|
||||
}
|
||||
|
||||
myConceptMapGroupDao.deleteTermConceptMapGroupById(group.getId());
|
||||
}
|
||||
|
||||
myConceptMapDao.deleteTermConceptMapById(existingTermConceptMap.getId());
|
||||
ourLog.info("Done deleting existing TermConceptMap {} and its children.", existingTermConceptMap.getId());
|
||||
|
||||
ourLog.info("Flushing...");
|
||||
myConceptMapGroupElementTargetDao.flush();
|
||||
|
|
|
@ -47,7 +47,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou
|
|||
* See #778
|
||||
*/
|
||||
@Test
|
||||
public void testReadingObservationAccessRight() throws IOException {
|
||||
public void testReadingObservationAccessRight() {
|
||||
Practitioner practitioner1 = new Practitioner();
|
||||
final IIdType practitionerId1 = ourClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
|
@ -105,7 +105,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou
|
|||
* See #667
|
||||
*/
|
||||
@Test
|
||||
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException {
|
||||
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() {
|
||||
Patient pt1 = new Patient();
|
||||
pt1.setActive(true);
|
||||
final IIdType pid1 = ourClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhir.jpa.provider.dstu3;
|
||||
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent;
|
||||
|
@ -29,6 +30,37 @@ public class ResourceProviderDstu3ConceptMapTest extends BaseResourceProviderDst
|
|||
myConceptMapId = myConceptMapDao.create(createConceptMap(), mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreExistingTermConceptMapAndChildren() {
|
||||
ConceptMap conceptMap = createConceptMap();
|
||||
|
||||
MethodOutcome methodOutcome = ourClient
|
||||
.update()
|
||||
.resource(conceptMap)
|
||||
.conditional()
|
||||
.where(ConceptMap.URL.matches().value(conceptMap.getUrl()))
|
||||
.execute();
|
||||
|
||||
assertNull(methodOutcome.getCreated());
|
||||
assertEquals("1", methodOutcome.getId().getVersionIdPart());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreUpdatedTermConceptMapAndChildren() {
|
||||
ConceptMap conceptMap = createConceptMap();
|
||||
conceptMap.getGroupFirstRep().getElementFirstRep().setCode("UPDATED_CODE");
|
||||
|
||||
MethodOutcome methodOutcome = ourClient
|
||||
.update()
|
||||
.resource(conceptMap)
|
||||
.conditional()
|
||||
.where(ConceptMap.URL.matches().value(conceptMap.getUrl()))
|
||||
.execute();
|
||||
|
||||
assertNull(methodOutcome.getCreated());
|
||||
assertEquals("2", methodOutcome.getId().getVersionIdPart());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTranslateByCodeSystemsAndSourceCodeOneToMany() {
|
||||
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
|
||||
|
|
|
@ -44,7 +44,7 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
|
|||
* See #778
|
||||
*/
|
||||
@Test
|
||||
public void testReadingObservationAccessRight() throws IOException {
|
||||
public void testReadingObservationAccessRight() {
|
||||
Practitioner practitioner1 = new Practitioner();
|
||||
final IIdType practitionerId1 = myClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
|
@ -102,7 +102,7 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
|
|||
* See #667
|
||||
*/
|
||||
@Test
|
||||
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException {
|
||||
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() {
|
||||
Patient pt1 = new Patient();
|
||||
pt1.setActive(true);
|
||||
final IIdType pid1 = myClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
|
@ -29,6 +30,37 @@ public class ResourceProviderR4ConceptMapTest extends BaseResourceProviderR4Test
|
|||
myConceptMapId = myConceptMapDao.create(createConceptMap(), mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreExistingTermConceptMapAndChildren() {
|
||||
ConceptMap conceptMap = createConceptMap();
|
||||
|
||||
MethodOutcome methodOutcome = myClient
|
||||
.update()
|
||||
.resource(conceptMap)
|
||||
.conditional()
|
||||
.where(ConceptMap.URL.matches().value(conceptMap.getUrl()))
|
||||
.execute();
|
||||
|
||||
assertNull(methodOutcome.getCreated());
|
||||
assertEquals("1", methodOutcome.getId().getVersionIdPart());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreUpdatedTermConceptMapAndChildren() {
|
||||
ConceptMap conceptMap = createConceptMap();
|
||||
conceptMap.getGroupFirstRep().getElementFirstRep().setCode("UPDATED_CODE");
|
||||
|
||||
MethodOutcome methodOutcome = myClient
|
||||
.update()
|
||||
.resource(conceptMap)
|
||||
.conditional()
|
||||
.where(ConceptMap.URL.matches().value(conceptMap.getUrl()))
|
||||
.execute();
|
||||
|
||||
assertNull(methodOutcome.getCreated());
|
||||
assertEquals("2", methodOutcome.getId().getVersionIdPart());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTranslateByCodeSystemsAndSourceCodeOneToMany() {
|
||||
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @see AuthorizationInterceptor#setFlags(Collection)
|
||||
*/
|
||||
public enum AuthorizationFlagsEnum {
|
||||
|
||||
/**
|
||||
* If this flag is set, attempts to perform read operations
|
||||
* (read/search/history) will be matched by the interceptor before
|
||||
* the method handler is called.
|
||||
* <p>
|
||||
* For example, suppose a rule set is in place that only allows read
|
||||
* access to compartment <code>Patient/123</code>. With this flag set,
|
||||
* any attempts
|
||||
* to perform a FHIR read/search/history operation will be permitted
|
||||
* to proceed to the method handler, and responses will be blocked
|
||||
* by the AuthorizationInterceptor if the response contains a resource
|
||||
* that is not in the given compartment.
|
||||
* </p>
|
||||
* <p>
|
||||
* Setting this flag is less secure, since the interceptor can potentially leak
|
||||
* information about the existence of data, but it is useful in some
|
||||
* scenarios.
|
||||
* </p>
|
||||
*
|
||||
* @since This flag has existed since HAPI FHIR 3.5.0. Prior to this
|
||||
* version, this flag was the default and there was no ability to
|
||||
* proactively block compartment read access.
|
||||
*/
|
||||
NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS;
|
||||
|
||||
}
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
* 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.
|
||||
|
@ -28,6 +28,7 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
|||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
||||
import ca.uhn.fhir.util.CoverageIgnore;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
@ -35,12 +36,12 @@ import org.hl7.fhir.instance.model.api.IBaseBundle;
|
|||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
|
||||
|
@ -56,9 +57,10 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
|||
*/
|
||||
public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter implements IRuleApplier {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AuthorizationInterceptor.class);
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptor.class);
|
||||
|
||||
private PolicyEnum myDefaultPolicy = PolicyEnum.DENY;
|
||||
private Set<AuthorizationFlagsEnum> myFlags = Collections.emptySet();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -92,11 +94,12 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
|||
public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId,
|
||||
IBaseResource theOutputResource) {
|
||||
List<IAuthRule> rules = buildRuleList(theRequestDetails);
|
||||
Set<AuthorizationFlagsEnum> flags = getFlags();
|
||||
ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation);
|
||||
|
||||
Verdict verdict = null;
|
||||
for (IAuthRule nextRule : rules) {
|
||||
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, this);
|
||||
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, this, flags);
|
||||
if (verdict != null) {
|
||||
ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision());
|
||||
break;
|
||||
|
@ -105,7 +108,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
|||
|
||||
if (verdict == null) {
|
||||
ourLog.trace("No rules returned a decision, applying default {}", myDefaultPolicy);
|
||||
return new Verdict(myDefaultPolicy, null);
|
||||
return new Verdict(getDefaultPolicy(), null);
|
||||
}
|
||||
|
||||
return verdict;
|
||||
|
@ -206,6 +209,28 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
|||
myDefaultPolicy = theDefaultPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* This property configures any flags affecting how authorization is
|
||||
* applied. By default no flags are applied.
|
||||
*
|
||||
* @see #setFlags(Collection)
|
||||
*/
|
||||
public Set<AuthorizationFlagsEnum> getFlags() {
|
||||
return Collections.unmodifiableSet(myFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* This property configures any flags affecting how authorization is
|
||||
* applied. By default no flags are applied.
|
||||
*
|
||||
* @param theFlags The flags (must not be null)
|
||||
* @see #setFlags(Collection)
|
||||
*/
|
||||
public AuthorizationInterceptor setFlags(AuthorizationFlagsEnum... theFlags) {
|
||||
Validate.notNull(theFlags, "theFlags must not be null");
|
||||
return setFlags(Lists.newArrayList(theFlags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an access control verdict of {@link PolicyEnum#DENY}.
|
||||
* <p>
|
||||
|
@ -325,6 +350,19 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
|||
handleUserOperation(theRequest, theNewResource, RestOperationTypeEnum.UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This property configures any flags affecting how authorization is
|
||||
* applied. By default no flags are applied.
|
||||
*
|
||||
* @param theFlags The flags (must not be null)
|
||||
* @see #setFlags(AuthorizationFlagsEnum...)
|
||||
*/
|
||||
public AuthorizationInterceptor setFlags(Collection<AuthorizationFlagsEnum> theFlags) {
|
||||
Validate.notNull(theFlags, "theFlags must not be null");
|
||||
myFlags = new HashSet<>(theFlags);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException failForDstu1() {
|
||||
return new UnsupportedOperationException("Use of this interceptor on DSTU1 servers is not supportd");
|
||||
}
|
||||
|
@ -333,7 +371,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
|||
if (theResponseObject == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
List<IBaseResource> retVal;
|
||||
|
||||
boolean isContainer = false;
|
||||
|
@ -342,11 +380,11 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
|||
} else if (theResponseObject instanceof IBaseParameters) {
|
||||
isContainer = true;
|
||||
}
|
||||
|
||||
|
||||
if (!isContainer) {
|
||||
return Collections.singletonList(theResponseObject);
|
||||
}
|
||||
|
||||
|
||||
retVal = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class);
|
||||
|
||||
// Exclude the container
|
||||
|
|
|
@ -27,6 +27,15 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Note: At this time, this interface is considered internal API to HAPI FHIR,
|
||||
* and is subject to change without warning. Create your own implementations at
|
||||
* your own risk. If you have use cases that are not met by the current
|
||||
* implementation, please consider raising them on the HAPI FHIR
|
||||
* Google Group.
|
||||
*/
|
||||
public interface IAuthRule {
|
||||
|
||||
/**
|
||||
|
@ -44,9 +53,10 @@ public interface IAuthRule {
|
|||
* @param theRuleApplier
|
||||
* The rule applying module (this can be used by rules to apply the rule set to
|
||||
* nested objects in the request, such as nested requests in a transaction)
|
||||
* @param theFlags
|
||||
* @return Returns a policy decision, or <code>null</code> if the rule does not apply
|
||||
*/
|
||||
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier);
|
||||
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags);
|
||||
|
||||
/**
|
||||
* Returns a name for this rule, to be used in logs and error messages
|
||||
|
|
|
@ -29,10 +29,11 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
class OperationRule extends BaseRule implements IAuthRule {
|
||||
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenentApplicabilityChecker;
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
private String myOperationName;
|
||||
private boolean myAppliesToServer;
|
||||
private HashSet<Class<? extends IBaseResource>> myAppliesToTypes;
|
||||
|
@ -75,17 +76,25 @@ class OperationRule extends BaseRule implements IAuthRule {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
|
||||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
|
||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||
|
||||
if (myTenentApplicabilityChecker != null) {
|
||||
if (!myTenentApplicabilityChecker.applies(theRequestDetails)) {
|
||||
if (myTenantApplicabilityChecker != null) {
|
||||
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
boolean applies = false;
|
||||
switch (theOperation) {
|
||||
case ADD_TAGS:
|
||||
case DELETE_TAGS:
|
||||
case GET_TAGS:
|
||||
case GET_PAGE:
|
||||
case GRAPHQL_REQUEST:
|
||||
// These things can't be tracked by the AuthorizationInterceptor
|
||||
// at this time
|
||||
return null;
|
||||
case EXTENDED_OPERATION_SERVER:
|
||||
if (myAppliesToServer || myAppliesAtAnyLevel) {
|
||||
applies = true;
|
||||
|
@ -130,6 +139,40 @@ class OperationRule extends BaseRule implements IAuthRule {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case CREATE:
|
||||
break;
|
||||
case DELETE:
|
||||
break;
|
||||
case HISTORY_INSTANCE:
|
||||
break;
|
||||
case HISTORY_SYSTEM:
|
||||
break;
|
||||
case HISTORY_TYPE:
|
||||
break;
|
||||
case READ:
|
||||
break;
|
||||
case SEARCH_SYSTEM:
|
||||
break;
|
||||
case SEARCH_TYPE:
|
||||
break;
|
||||
case TRANSACTION:
|
||||
break;
|
||||
case UPDATE:
|
||||
break;
|
||||
case VALIDATE:
|
||||
break;
|
||||
case VREAD:
|
||||
break;
|
||||
case METADATA:
|
||||
break;
|
||||
case META_ADD:
|
||||
break;
|
||||
case META:
|
||||
break;
|
||||
case META_DELETE:
|
||||
break;
|
||||
case PATCH:
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -160,8 +203,8 @@ class OperationRule extends BaseRule implements IAuthRule {
|
|||
myOperationName = theOperationName;
|
||||
}
|
||||
|
||||
public void setTenentApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenentApplicabilityChecker) {
|
||||
myTenentApplicabilityChecker = theTenentApplicabilityChecker;
|
||||
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -174,7 +174,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
myOpRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
|
||||
}
|
||||
if (myOperationRule != null) {
|
||||
myOperationRule.setTenentApplicabilityChecker(myTenantApplicabilityChecker);
|
||||
myOperationRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
|
|||
|
||||
@Override
|
||||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
|
||||
IRuleApplier theRuleApplier) {
|
||||
IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
|
||||
|
||||
if (theInputResourceId != null) {
|
||||
return null;
|
||||
|
|
|
@ -1,5 +1,30 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import org.apache.commons.codec.binary.StringUtils;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/*
|
||||
|
@ -11,9 +36,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.
|
||||
|
@ -22,26 +47,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.rest.api.*;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
|
||||
class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
|
||||
private AppliesTypeEnum myAppliesTo;
|
||||
|
@ -54,13 +59,16 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
private List<IIdType> myAppliesToInstances;
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public RuleImplOp(String theRuleName) {
|
||||
super(theRuleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
|
||||
IRuleApplier theRuleApplier) {
|
||||
IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
|
||||
|
||||
if (myTenantApplicabilityChecker != null) {
|
||||
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
|
||||
|
@ -73,232 +81,335 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
IBaseResource appliesToResource;
|
||||
IIdType appliesToResourceId = null;
|
||||
String appliesToResourceType = null;
|
||||
Map<String, String[]> appliesToSearchParams = null;
|
||||
switch (myOp) {
|
||||
case READ:
|
||||
if (theOutputResource == null) {
|
||||
switch (theOperation) {
|
||||
case READ:
|
||||
case VREAD:
|
||||
appliesToResourceId = theInputResourceId;
|
||||
appliesToResourceType = theInputResourceId.getResourceType();
|
||||
break;
|
||||
case SEARCH_SYSTEM:
|
||||
case SEARCH_TYPE:
|
||||
case HISTORY_INSTANCE:
|
||||
case HISTORY_SYSTEM:
|
||||
case HISTORY_TYPE:
|
||||
case READ:
|
||||
if (theOutputResource == null) {
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
default:
|
||||
return null;
|
||||
|
||||
switch (theOperation) {
|
||||
case READ:
|
||||
case VREAD:
|
||||
appliesToResourceId = theInputResourceId;
|
||||
appliesToResourceType = theInputResourceId.getResourceType();
|
||||
break;
|
||||
case SEARCH_SYSTEM:
|
||||
case HISTORY_SYSTEM:
|
||||
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
}
|
||||
break;
|
||||
case SEARCH_TYPE:
|
||||
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
}
|
||||
appliesToResourceType = theRequestDetails.getResourceName();
|
||||
appliesToSearchParams = theRequestDetails.getParameters();
|
||||
break;
|
||||
case HISTORY_TYPE:
|
||||
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
}
|
||||
appliesToResourceType = theRequestDetails.getResourceName();
|
||||
break;
|
||||
case HISTORY_INSTANCE:
|
||||
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
}
|
||||
appliesToResourceId = theInputResourceId;
|
||||
break;
|
||||
case GET_PAGE:
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
|
||||
// None of the following are checked on the way in
|
||||
case ADD_TAGS:
|
||||
case DELETE_TAGS:
|
||||
case GET_TAGS:
|
||||
case GRAPHQL_REQUEST:
|
||||
case EXTENDED_OPERATION_SERVER:
|
||||
case EXTENDED_OPERATION_TYPE:
|
||||
case EXTENDED_OPERATION_INSTANCE:
|
||||
case CREATE:
|
||||
case DELETE:
|
||||
case TRANSACTION:
|
||||
case UPDATE:
|
||||
case VALIDATE:
|
||||
case METADATA:
|
||||
case META_ADD:
|
||||
case META:
|
||||
case META_DELETE:
|
||||
case PATCH:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
appliesToResource = theOutputResource;
|
||||
if (theOutputResource != null) {
|
||||
appliesToResourceId = theOutputResource.getIdElement();
|
||||
}
|
||||
}
|
||||
appliesToResource = theOutputResource;
|
||||
if (theOutputResource != null) {
|
||||
appliesToResourceId = theOutputResource.getIdElement();
|
||||
}
|
||||
break;
|
||||
case WRITE:
|
||||
if (theInputResource == null && theInputResourceId == null) {
|
||||
return null;
|
||||
}
|
||||
switch (theOperation) {
|
||||
case CREATE:
|
||||
case UPDATE:
|
||||
case ADD_TAGS:
|
||||
case DELETE_TAGS:
|
||||
case META_ADD:
|
||||
case META_DELETE:
|
||||
case PATCH:
|
||||
appliesToResource = theInputResource;
|
||||
appliesToResourceId = theInputResourceId;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case DELETE:
|
||||
if (theOperation == RestOperationTypeEnum.DELETE) {
|
||||
if (theInputResource == null) {
|
||||
return newVerdict();
|
||||
}
|
||||
appliesToResource = theInputResource;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case BATCH:
|
||||
case TRANSACTION:
|
||||
if (!(theOperation == RestOperationTypeEnum.TRANSACTION)) {
|
||||
return null;
|
||||
}
|
||||
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
|
||||
if (getMode() == PolicyEnum.DENY) {
|
||||
return new Verdict(PolicyEnum.DENY, this);
|
||||
}
|
||||
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
|
||||
Verdict verdict = null;
|
||||
for (BundleEntryParts nextPart : inputResources) {
|
||||
|
||||
IBaseResource inputResource = nextPart.getResource();
|
||||
RestOperationTypeEnum operation = null;
|
||||
if (nextPart.getRequestType() == RequestTypeEnum.GET) {
|
||||
continue;
|
||||
}
|
||||
if (nextPart.getRequestType() == RequestTypeEnum.POST) {
|
||||
operation = RestOperationTypeEnum.CREATE;
|
||||
} else if (nextPart.getRequestType() == RequestTypeEnum.PUT) {
|
||||
operation = RestOperationTypeEnum.UPDATE;
|
||||
} else {
|
||||
throw new InvalidRequestException("Can not handle transaction with operation of type " + nextPart.getRequestType());
|
||||
}
|
||||
|
||||
/*
|
||||
* This is basically just being conservative - Be careful of transactions containing
|
||||
* nested operations and nested transactions. We block the by default. At some point
|
||||
* it would be nice to be more nuanced here.
|
||||
*/
|
||||
RuntimeResourceDefinition resourceDef = ctx.getResourceDefinition(nextPart.getResource());
|
||||
if ("Parameters".equals(resourceDef.getName()) || "Bundle".equals(resourceDef.getName())) {
|
||||
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
|
||||
}
|
||||
|
||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null, null);
|
||||
if (newVerdict == null) {
|
||||
continue;
|
||||
} else if (verdict == null) {
|
||||
verdict = newVerdict;
|
||||
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
|
||||
verdict = newVerdict;
|
||||
}
|
||||
}
|
||||
return verdict;
|
||||
} else if (theOutputResource != null) {
|
||||
|
||||
List<IBaseResource> outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(theOutputResource, theRequestDetails.getFhirContext());
|
||||
|
||||
Verdict verdict = null;
|
||||
for (IBaseResource nextResource : outputResources) {
|
||||
if (nextResource == null) {
|
||||
continue;
|
||||
}
|
||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextResource);
|
||||
if (newVerdict == null) {
|
||||
continue;
|
||||
} else if (verdict == null) {
|
||||
verdict = newVerdict;
|
||||
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
|
||||
verdict = newVerdict;
|
||||
}
|
||||
}
|
||||
return verdict;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case ALLOW_ALL:
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
case DENY_ALL:
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
return new Verdict(PolicyEnum.DENY, this);
|
||||
case METADATA:
|
||||
if (theOperation == RestOperationTypeEnum.METADATA) {
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
case WRITE:
|
||||
if (theInputResource == null && theInputResourceId == null) {
|
||||
return null;
|
||||
}
|
||||
return newVerdict();
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
// Should not happen
|
||||
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
|
||||
}
|
||||
switch (theOperation) {
|
||||
case CREATE:
|
||||
case UPDATE:
|
||||
case ADD_TAGS:
|
||||
case DELETE_TAGS:
|
||||
case META_ADD:
|
||||
case META_DELETE:
|
||||
case PATCH:
|
||||
appliesToResource = theInputResource;
|
||||
appliesToResourceId = theInputResourceId;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case DELETE:
|
||||
if (theOperation == RestOperationTypeEnum.DELETE) {
|
||||
if (theInputResource == null) {
|
||||
return newVerdict();
|
||||
}
|
||||
appliesToResource = theInputResource;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case BATCH:
|
||||
case TRANSACTION:
|
||||
if (!(theOperation == RestOperationTypeEnum.TRANSACTION)) {
|
||||
return null;
|
||||
}
|
||||
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
|
||||
if (getMode() == PolicyEnum.DENY) {
|
||||
return new Verdict(PolicyEnum.DENY, this);
|
||||
}
|
||||
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
|
||||
Verdict verdict = null;
|
||||
for (BundleEntryParts nextPart : inputResources) {
|
||||
|
||||
switch (myAppliesTo) {
|
||||
case INSTANCES:
|
||||
if (appliesToResourceId != null) {
|
||||
for (IIdType next : myAppliesToInstances) {
|
||||
if (isNotBlank(next.getResourceType())) {
|
||||
if (!next.getResourceType().equals(appliesToResourceId.getResourceType())) {
|
||||
IBaseResource inputResource = nextPart.getResource();
|
||||
RestOperationTypeEnum operation = null;
|
||||
if (nextPart.getRequestType() == RequestTypeEnum.GET) {
|
||||
continue;
|
||||
}
|
||||
if (nextPart.getRequestType() == RequestTypeEnum.POST) {
|
||||
operation = RestOperationTypeEnum.CREATE;
|
||||
} else if (nextPart.getRequestType() == RequestTypeEnum.PUT) {
|
||||
operation = RestOperationTypeEnum.UPDATE;
|
||||
} else {
|
||||
throw new InvalidRequestException("Can not handle transaction with operation of type " + nextPart.getRequestType());
|
||||
}
|
||||
|
||||
/*
|
||||
* This is basically just being conservative - Be careful of transactions containing
|
||||
* nested operations and nested transactions. We block the by default. At some point
|
||||
* it would be nice to be more nuanced here.
|
||||
*/
|
||||
RuntimeResourceDefinition resourceDef = ctx.getResourceDefinition(nextPart.getResource());
|
||||
if ("Parameters".equals(resourceDef.getName()) || "Bundle".equals(resourceDef.getName())) {
|
||||
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
|
||||
}
|
||||
|
||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null, null);
|
||||
if (newVerdict == null) {
|
||||
continue;
|
||||
} else if (verdict == null) {
|
||||
verdict = newVerdict;
|
||||
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
|
||||
verdict = newVerdict;
|
||||
}
|
||||
}
|
||||
if (!next.getIdPart().equals(appliesToResourceId.getIdPart())) {
|
||||
continue;
|
||||
return verdict;
|
||||
} else if (theOutputResource != null) {
|
||||
|
||||
List<IBaseResource> outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(theOutputResource, theRequestDetails.getFhirContext());
|
||||
|
||||
Verdict verdict = null;
|
||||
for (IBaseResource nextResource : outputResources) {
|
||||
if (nextResource == null) {
|
||||
continue;
|
||||
}
|
||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextResource);
|
||||
if (newVerdict == null) {
|
||||
continue;
|
||||
} else if (verdict == null) {
|
||||
verdict = newVerdict;
|
||||
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
|
||||
verdict = newVerdict;
|
||||
}
|
||||
}
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
return newVerdict();
|
||||
return verdict;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case ALL_RESOURCES:
|
||||
if (appliesToResourceType != null) {
|
||||
case ALLOW_ALL:
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
}
|
||||
break;
|
||||
case TYPES:
|
||||
if (appliesToResource != null) {
|
||||
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
|
||||
case DENY_ALL:
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (appliesToResourceId != null && appliesToResourceId.hasResourceType()) {
|
||||
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
|
||||
if (myAppliesToTypes.contains(type) == false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (appliesToResourceType != null) {
|
||||
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceType).getImplementingClass();
|
||||
if (myAppliesToTypes.contains(type)) {
|
||||
return new Verdict(PolicyEnum.DENY, this);
|
||||
case METADATA:
|
||||
if (theOperation == RestOperationTypeEnum.METADATA) {
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
return newVerdict();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||
return null;
|
||||
default:
|
||||
// Should not happen
|
||||
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
|
||||
}
|
||||
|
||||
switch (myAppliesTo) {
|
||||
case INSTANCES:
|
||||
if (appliesToResourceId != null) {
|
||||
for (IIdType next : myAppliesToInstances) {
|
||||
if (isNotBlank(next.getResourceType())) {
|
||||
if (!next.getResourceType().equals(appliesToResourceId.getResourceType())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!next.getIdPart().equals(appliesToResourceId.getIdPart())) {
|
||||
continue;
|
||||
}
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
return newVerdict();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case ALL_RESOURCES:
|
||||
if (appliesToResourceType != null) {
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TYPES:
|
||||
if (appliesToResource != null) {
|
||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (appliesToResourceId != null && appliesToResourceId.hasResourceType()) {
|
||||
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
|
||||
if (myAppliesToTypes.contains(type) == false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (appliesToResourceType != null) {
|
||||
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceType).getImplementingClass();
|
||||
if (myAppliesToTypes.contains(type)) {
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
} else if (myClassifierType == ClassifierTypeEnum.IN_COMPARTMENT) {
|
||||
// ok we'll check below
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||
}
|
||||
|
||||
switch (myClassifierType) {
|
||||
case ANY_ID:
|
||||
break;
|
||||
case IN_COMPARTMENT:
|
||||
FhirTerser t = ctx.newTerser();
|
||||
boolean foundMatch = false;
|
||||
for (IIdType next : myClassifierCompartmentOwners) {
|
||||
if (appliesToResource != null) {
|
||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesToResource, next)) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
case ANY_ID:
|
||||
break;
|
||||
case IN_COMPARTMENT:
|
||||
FhirTerser t = ctx.newTerser();
|
||||
boolean foundMatch = false;
|
||||
for (IIdType next : myClassifierCompartmentOwners) {
|
||||
if (appliesToResource != null) {
|
||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesToResource, next)) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (appliesToResourceId != null && appliesToResourceId.hasResourceType() && appliesToResourceId.hasIdPart()) {
|
||||
if (appliesToResourceId.toUnqualifiedVersionless().getValue().equals(next.toUnqualifiedVersionless().getValue())) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the client has permission to read compartment
|
||||
* Patient/ABC, then a search for Patient?_id=Patient/ABC
|
||||
* should be permitted. This is kind of a one-off case, but
|
||||
* it makes sense.
|
||||
*/
|
||||
if (next.getResourceType().equals(appliesToResourceType)) {
|
||||
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(appliesToSearchParams, next, IAnyResource.SP_RES_ID);
|
||||
if (verdict != null) {
|
||||
return verdict;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're trying to read a resource that could potentially be
|
||||
* in the given compartment, we'll let the request through and
|
||||
* catch any issues on the response.
|
||||
*
|
||||
* This is less than perfect, but it's the best we can do-
|
||||
* If the user is allowed to see compartment "Patient/123" and
|
||||
* the client is requesting to read a CarePlan, there is nothing
|
||||
* in the request URL that indicates whether or not the CarePlan
|
||||
* might be in the given compartment.
|
||||
*/
|
||||
if (isNotBlank(appliesToResourceType)) {
|
||||
RuntimeResourceDefinition sourceDef = theRequestDetails.getFhirContext().getResourceDefinition(appliesToResourceType);
|
||||
String compartmentOwnerResourceType = next.getResourceType();
|
||||
if (!StringUtils.equals(appliesToResourceType, compartmentOwnerResourceType)) {
|
||||
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(compartmentOwnerResourceType);
|
||||
if (params.isEmpty() == false) {
|
||||
|
||||
/*
|
||||
* If this is a search, we can at least check whether
|
||||
* the client has requested a search parameter that
|
||||
* would match the given compartment. In this case, this
|
||||
* is a very effective mechanism.
|
||||
*/
|
||||
if (appliesToSearchParams != null && !theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
for (RuntimeSearchParam nextRuntimeSearchParam : params) {
|
||||
String name = nextRuntimeSearchParam.getName();
|
||||
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(appliesToSearchParams, next, name);
|
||||
if (verdict != null) {
|
||||
return verdict;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (appliesToResourceId != null && appliesToResourceId.hasResourceType() && appliesToResourceId.hasIdPart()) {
|
||||
if (appliesToResourceId.toUnqualifiedVersionless().getValue().equals(next.toUnqualifiedVersionless().getValue())) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
if (!foundMatch) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!foundMatch) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||
}
|
||||
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
|
@ -308,22 +419,32 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
return newVerdict();
|
||||
}
|
||||
|
||||
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
private Verdict checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(Map<String, String[]> theSearchParams, IIdType theCompartmentOwner, String theSearchParamName) {
|
||||
Verdict verdict = null;
|
||||
if (theSearchParams != null) {
|
||||
String[] values = theSearchParams.get(theSearchParamName);
|
||||
if (values != null) {
|
||||
for (String nextParameterValue : values) {
|
||||
if (nextParameterValue.equals(theCompartmentOwner.getValue())) {
|
||||
verdict = new Verdict(PolicyEnum.ALLOW, this);
|
||||
break;
|
||||
}
|
||||
if (nextParameterValue.equals(theCompartmentOwner.getIdPart())) {
|
||||
verdict = new Verdict(PolicyEnum.ALLOW, this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return verdict;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||
builder.append("op", myOp);
|
||||
builder.append("transactionAppliesToOp", myTransactionAppliesToOp);
|
||||
builder.append("appliesTo", myAppliesTo);
|
||||
builder.append("appliesToTypes", myAppliesToTypes);
|
||||
builder.append("appliesToTenant", myTenantApplicabilityChecker);
|
||||
builder.append("classifierCompartmentName", myClassifierCompartmentName);
|
||||
builder.append("classifierCompartmentOwners", myClassifierCompartmentOwners);
|
||||
builder.append("classifierType", myClassifierType);
|
||||
return builder.toString();
|
||||
public TransactionAppliesToEnum getTransactionAppliesToOp() {
|
||||
return myTransactionAppliesToOp;
|
||||
}
|
||||
|
||||
public void setTransactionAppliesToOp(TransactionAppliesToEnum theOp) {
|
||||
myTransactionAppliesToOp = theOp;
|
||||
}
|
||||
|
||||
private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) {
|
||||
|
@ -334,23 +455,23 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
IBaseBundle request = (IBaseBundle) theInputResource;
|
||||
String bundleType = BundleUtil.getBundleType(theContext, request);
|
||||
switch (theOp) {
|
||||
case TRANSACTION:
|
||||
return "transaction".equals(bundleType);
|
||||
case BATCH:
|
||||
return "batch".equals(bundleType);
|
||||
default:
|
||||
return false;
|
||||
case TRANSACTION:
|
||||
return "transaction".equals(bundleType);
|
||||
case BATCH:
|
||||
return "batch".equals(bundleType);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public TransactionAppliesToEnum getTransactionAppliesToOp() {
|
||||
return myTransactionAppliesToOp;
|
||||
}
|
||||
|
||||
public void setAppliesTo(AppliesTypeEnum theAppliesTo) {
|
||||
myAppliesTo = theAppliesTo;
|
||||
}
|
||||
|
||||
public void setAppliesToInstances(List<IIdType> theAppliesToInstances) {
|
||||
myAppliesToInstances = theAppliesToInstances;
|
||||
}
|
||||
|
||||
public void setAppliesToTypes(Set<?> theAppliesToTypes) {
|
||||
myAppliesToTypes = theAppliesToTypes;
|
||||
}
|
||||
|
@ -372,12 +493,22 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
return this;
|
||||
}
|
||||
|
||||
public void setTransactionAppliesToOp(TransactionAppliesToEnum theOp) {
|
||||
myTransactionAppliesToOp = theOp;
|
||||
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
}
|
||||
|
||||
public void setAppliesToInstances(List<IIdType> theAppliesToInstances) {
|
||||
myAppliesToInstances = theAppliesToInstances;
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||
builder.append("op", myOp);
|
||||
builder.append("transactionAppliesToOp", myTransactionAppliesToOp);
|
||||
builder.append("appliesTo", myAppliesTo);
|
||||
builder.append("appliesToTypes", myAppliesToTypes);
|
||||
builder.append("appliesToTenant", myTenantApplicabilityChecker);
|
||||
builder.append("classifierCompartmentName", myClassifierCompartmentName);
|
||||
builder.append("classifierCompartmentOwners", myClassifierCompartmentOwners);
|
||||
builder.append("classifierType", myClassifierType);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
|
@ -65,7 +66,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
@Before
|
||||
public void before() {
|
||||
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.NEVER);
|
||||
for (IServerInterceptor next : new ArrayList<IServerInterceptor>(ourServlet.getInterceptors())) {
|
||||
for (IServerInterceptor next : new ArrayList<>(ourServlet.getInterceptors())) {
|
||||
ourServlet.unregisterInterceptor(next);
|
||||
}
|
||||
ourReturn = null;
|
||||
|
@ -1166,8 +1167,8 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
}
|
||||
|
||||
|
@ -1177,8 +1178,8 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1"))
|
||||
.build();
|
||||
.allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdDt("Patient/1"))
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1187,13 +1188,13 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
String respString;
|
||||
Bundle respBundle;
|
||||
|
||||
ourReturn = new ArrayList<IResource>();
|
||||
ourReturn = new ArrayList<>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ourReturn.add(createPatient(1));
|
||||
ourReturn.add(createObservation(i, "Patient/1"));
|
||||
}
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
respString = extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -1201,7 +1202,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
|
||||
assertEquals(5, respBundle.getEntry().size());
|
||||
assertEquals(10, respBundle.getTotal().intValue());
|
||||
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertNotNull(respBundle.getLink("next"));
|
||||
|
||||
// Load next page
|
||||
|
@ -1215,7 +1216,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
|
||||
assertEquals(5, respBundle.getEntry().size());
|
||||
assertEquals(10, respBundle.getTotal().intValue());
|
||||
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertEquals("Observation/5", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertNull(respBundle.getLink("next"));
|
||||
|
||||
}
|
||||
|
@ -1226,8 +1227,8 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1"))
|
||||
.build();
|
||||
.allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdDt("Patient/1"))
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1236,16 +1237,16 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
String respString;
|
||||
Bundle respBundle;
|
||||
|
||||
ourReturn = new ArrayList<IResource>();
|
||||
ourReturn = new ArrayList<>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ourReturn.add(createPatient(1));
|
||||
ourReturn.add(createObservation(i, "Patient/1"));
|
||||
}
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ourReturn.add(createPatient(2));
|
||||
for (int i = 5; i < 10; i++) {
|
||||
ourReturn.add(createObservation(i, "Patient/2"));
|
||||
}
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
respString = extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -1253,7 +1254,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
|
||||
assertEquals(5, respBundle.getEntry().size());
|
||||
assertEquals(10, respBundle.getTotal().intValue());
|
||||
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertNotNull(respBundle.getLink("next"));
|
||||
|
||||
// Load next page
|
||||
|
@ -1261,7 +1262,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
ourHitMethod = false;
|
||||
httpGet = new HttpGet(respBundle.getLink("next").getUrl());
|
||||
status = ourClient.execute(httpGet);
|
||||
respString = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
|
@ -1283,7 +1284,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
ourReturn = Arrays.asList(createPatient(2));
|
||||
ourReturn = Collections.singletonList(createPatient(2));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
|
||||
status = ourClient.execute(httpGet);
|
||||
|
@ -1291,9 +1292,9 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
ourLog.info(response);
|
||||
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourReturn = Arrays.asList(createObservation(10, "Patient/2"));
|
||||
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
|
@ -1303,7 +1304,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Arrays.asList(createCarePlan(10, "Patient/2"));
|
||||
ourReturn = Collections.singletonList(createCarePlan(10, "Patient/2"));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
|
@ -1321,7 +1322,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
ourLog.info(response);
|
||||
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourReturn = Arrays.asList(createPatient(2), createObservation(10, "Patient/1"));
|
||||
ourHitMethod = false;
|
||||
|
@ -1331,7 +1332,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
ourLog.info(response);
|
||||
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
}
|
||||
|
||||
|
@ -1359,7 +1360,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
HttpPost httpPost;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Arrays.asList((IResource) output);
|
||||
ourReturn = Collections.singletonList(output);
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||
httpPost.setEntity(createFhirResourceEntity(input));
|
||||
|
@ -1419,6 +1420,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/1")));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.debug(response);
|
||||
assertEquals(201, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
}
|
||||
|
@ -1469,7 +1471,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
HttpDelete httpDelete;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Arrays.asList(createPatient(1));
|
||||
ourReturn = Collections.singletonList(createPatient(1));
|
||||
|
||||
ourHitMethod = false;
|
||||
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar");
|
||||
|
@ -1497,7 +1499,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
HttpDelete httpDelete;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Arrays.asList(createPatient(1));
|
||||
ourReturn = Collections.singletonList(createPatient(1));
|
||||
|
||||
ourHitMethod = false;
|
||||
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar");
|
||||
|
@ -1525,7 +1527,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
HttpResponse status;
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Arrays.asList(createPatient(2));
|
||||
ourReturn = Collections.singletonList(createPatient(2));
|
||||
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/2");
|
||||
status = ourClient.execute(httpDelete);
|
||||
extractResponseAndClose(status);
|
||||
|
@ -1533,7 +1535,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Arrays.asList(createPatient(1));
|
||||
ourReturn = Collections.singletonList(createPatient(1));
|
||||
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/1");
|
||||
status = ourClient.execute(httpDelete);
|
||||
extractResponseAndClose(status);
|
||||
|
@ -1665,6 +1667,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
httpPost.setEntity(createFhirResourceEntity(createPatient(null)));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.debug(response);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
|
@ -1703,6 +1706,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
httpPost.setEntity(createFhirResourceEntity(createPatient(null)));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.debug(response);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
|
@ -1718,7 +1722,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidInstanceIds() throws Exception {
|
||||
public void testInvalidInstanceIds() {
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance((String) null);
|
||||
fail();
|
||||
|
@ -2009,7 +2013,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
}
|
||||
|
||||
@Search()
|
||||
public List<IResource> search() {
|
||||
public List<IResource> search(@OptionalParam(name="subject")ReferenceParam theSubject) {
|
||||
ourHitMethod = true;
|
||||
return ourReturn;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,12 @@ public class FhirContextDstu3Test {
|
|||
assertEquals(FhirVersionEnum.DSTU3, ctx.getVersion().getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRuntimeSearchParamToString() {
|
||||
String val = ourCtx.getResourceDefinition("Patient").getSearchParam("gender").toString();
|
||||
assertEquals("RuntimeSearchParam[base=[Patient],name=gender,path=Patient.gender,id=<null>,uri=<null>]", val);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomTypeDoesntBecomeDefault() {
|
||||
FhirContext ctx = FhirContext.forDstu3();
|
||||
|
@ -69,7 +75,7 @@ public class FhirContextDstu3Test {
|
|||
final FhirContext ctx = FhirContext.forDstu3();
|
||||
|
||||
final int numThreads = 40;
|
||||
final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<Throwable>());
|
||||
final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<>());
|
||||
final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
|
||||
try {
|
||||
final CountDownLatch threadsReady = new CountDownLatch(numThreads);
|
||||
|
@ -77,19 +83,17 @@ public class FhirContextDstu3Test {
|
|||
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
threadPool.submit(
|
||||
new Runnable() {
|
||||
public void run() {
|
||||
threadsReady.countDown();
|
||||
try {
|
||||
threadsReady.await();
|
||||
RuntimeResourceDefinition def = ctx.getResourceDefinition("patient");
|
||||
ourLog.info(def.toString());
|
||||
assertNotNull(def);
|
||||
} catch (final Exception e) {
|
||||
exceptions.add(e);
|
||||
}
|
||||
threadsFinished.countDown();
|
||||
() -> {
|
||||
threadsReady.countDown();
|
||||
try {
|
||||
threadsReady.await();
|
||||
RuntimeResourceDefinition def = ctx.getResourceDefinition("patient");
|
||||
ourLog.info(def.toString());
|
||||
assertNotNull(def);
|
||||
} catch (final Exception e) {
|
||||
exceptions.add(e);
|
||||
}
|
||||
threadsFinished.countDown();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -108,18 +112,15 @@ public class FhirContextDstu3Test {
|
|||
* See #794
|
||||
*/
|
||||
@Test
|
||||
public void testInitializeThreadSafety2() throws InterruptedException {
|
||||
public void testInitializeThreadSafety2() {
|
||||
final FhirContext dstu3FhirContext = FhirContext.forDstu3();
|
||||
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
OperationOutcomeUtil.newInstance(dstu3FhirContext);
|
||||
ourLog.info("Have finished {}", count.incrementAndGet());
|
||||
}
|
||||
new Thread(() -> {
|
||||
OperationOutcomeUtil.newInstance(dstu3FhirContext);
|
||||
ourLog.info("Have finished {}", count.incrementAndGet());
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,9 +8,11 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||
import ca.uhn.fhir.rest.api.*;
|
||||
import ca.uhn.fhir.rest.api.server.IRequestOperationCallback;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.*;
|
||||
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
|
||||
|
@ -97,6 +99,16 @@ public class AuthorizationInterceptorR4Test {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private Resource createDiagnosticReport(Integer theId, String theSubjectId) {
|
||||
DiagnosticReport retVal = new DiagnosticReport();
|
||||
if (theId != null) {
|
||||
retVal.setId(new IdType("DiagnosticReport", (long) theId));
|
||||
}
|
||||
retVal.getCode().setText("OBS");
|
||||
retVal.setSubject(new Reference(theSubjectId));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private Organization createOrganization(int theIndex) {
|
||||
Organization retVal = new Organization();
|
||||
retVal.setId("" + theIndex);
|
||||
|
@ -287,6 +299,68 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder().allow().read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/123")).andThen().denyAll()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
|
||||
Patient patient;
|
||||
CarePlan carePlan;
|
||||
|
||||
// Unqualified
|
||||
patient = new Patient();
|
||||
patient.setId("123");
|
||||
carePlan = new CarePlan();
|
||||
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
|
||||
carePlan.getSubject().setResource(patient);
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(carePlan);
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
// Qualified
|
||||
patient = new Patient();
|
||||
patient.setId("Patient/123");
|
||||
carePlan = new CarePlan();
|
||||
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
|
||||
carePlan.getSubject().setResource(patient);
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(carePlan);
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
// Wrong one
|
||||
patient = new Patient();
|
||||
patient.setId("456");
|
||||
carePlan = new CarePlan();
|
||||
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
|
||||
carePlan.getSubject().setResource(patient);
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(carePlan);
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* #528
|
||||
*/
|
||||
|
@ -390,69 +464,6 @@ public class AuthorizationInterceptorR4Test {
|
|||
assertTrue(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder().allow().read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/123")).andThen().denyAll()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
|
||||
Patient patient;
|
||||
CarePlan carePlan;
|
||||
|
||||
// Unqualified
|
||||
patient = new Patient();
|
||||
patient.setId("123");
|
||||
carePlan = new CarePlan();
|
||||
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
|
||||
carePlan.getSubject().setResource(patient);
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(carePlan);
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
// Qualified
|
||||
patient = new Patient();
|
||||
patient.setId("Patient/123");
|
||||
carePlan = new CarePlan();
|
||||
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
|
||||
carePlan.getSubject().setResource(patient);
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(carePlan);
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
// Wrong one
|
||||
patient = new Patient();
|
||||
patient.setId("456");
|
||||
carePlan = new CarePlan();
|
||||
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
|
||||
carePlan.getSubject().setResource(patient);
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(carePlan);
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBatchWhenOnlyTransactionAllowed() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
|
@ -477,7 +488,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
HttpPost httpPost;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Collections.singletonList((Resource) output);
|
||||
ourReturn = Collections.singletonList(output);
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||
httpPost.setEntity(createFhirResourceEntity(input));
|
||||
|
@ -510,7 +521,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
HttpPost httpPost;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Collections.singletonList((Resource) output);
|
||||
ourReturn = Collections.singletonList(output);
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||
httpPost.setEntity(createFhirResourceEntity(input));
|
||||
|
@ -544,7 +555,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
ourReturn = Collections.singletonList((Resource) output);
|
||||
ourReturn = Collections.singletonList(output);
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||
httpPost.setEntity(createFhirResourceEntity(input));
|
||||
|
@ -1805,6 +1816,85 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentReadByPatientParam() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Collections.singletonList(createDiagnosticReport(1, "Patient/1"));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/DiagnosticReport?patient=Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createDiagnosticReport(1, "Patient/1"));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/DiagnosticReport?patient=1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createDiagnosticReport(1, "Patient/1"));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/DiagnosticReport?patient=Patient/2");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentReadByIdParam() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(1));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(1));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=Patient/2");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentRight() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
|
@ -1841,13 +1931,86 @@ public class AuthorizationInterceptorR4Test {
|
|||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentWrong() throws Exception {
|
||||
public void testReadByCompartmentWrongAllTypesProactiveBlockEnabledNoResponse() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
|
||||
.build();
|
||||
}
|
||||
}.setFlags());
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
ourReturn = Collections.emptyList();
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(404, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(404, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/999/_history");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentWrongProactiveBlockDisabled() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
|
@ -1856,7 +2019,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
.allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
|
||||
.build();
|
||||
}
|
||||
});
|
||||
}.setFlags(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS));
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
|
@ -1870,7 +2033,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
ourLog.info(response);
|
||||
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
|
||||
ourHitMethod = false;
|
||||
|
@ -1914,6 +2077,133 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentWrongProactiveBlockDisabledNoResponse() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen()
|
||||
.allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
|
||||
.build();
|
||||
}
|
||||
}.setFlags(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS));
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
ourReturn = Collections.emptyList();
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(404, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentWrongProactiveBlockEnabledNoResponse() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen()
|
||||
.allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
|
||||
.build();
|
||||
}
|
||||
}.setFlags());
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
ourReturn = Collections.emptyList();
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(404, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
// CarePlan could potentially be in the Patient/1 compartment but we don't
|
||||
// have any rules explicitly allowing CarePlan so it's blocked
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/999/_history");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByInstance() throws Exception {
|
||||
ourConditionalCreateId = "1";
|
||||
|
@ -1965,7 +2255,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1"))
|
||||
.allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
@ -1977,11 +2267,11 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
ourReturn = new ArrayList<>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ourReturn.add(createPatient(1));
|
||||
ourReturn.add(createObservation(i, "Patient/1"));
|
||||
}
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
respString = extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -1989,7 +2279,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
|
||||
assertEquals(5, respBundle.getEntry().size());
|
||||
assertEquals(10, respBundle.getTotal());
|
||||
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertNotNull(respBundle.getLink("next"));
|
||||
|
||||
// Load next page
|
||||
|
@ -2003,7 +2293,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
|
||||
assertEquals(5, respBundle.getEntry().size());
|
||||
assertEquals(10, respBundle.getTotal());
|
||||
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertEquals("Observation/5", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertNull(respBundle.getLink("next"));
|
||||
|
||||
}
|
||||
|
@ -2014,7 +2304,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1"))
|
||||
.allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
@ -2026,14 +2316,14 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
ourReturn = new ArrayList<>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ourReturn.add(createPatient(1));
|
||||
ourReturn.add(createObservation(i, "Patient/1"));
|
||||
}
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ourReturn.add(createPatient(2));
|
||||
for (int i = 5; i < 10; i++) {
|
||||
ourReturn.add(createObservation(i, "Patient/2"));
|
||||
}
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
respString = extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -2041,7 +2331,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
|
||||
assertEquals(5, respBundle.getEntry().size());
|
||||
assertEquals(10, respBundle.getTotal());
|
||||
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertNotNull(respBundle.getLink("next"));
|
||||
|
||||
// Load next page
|
||||
|
@ -2078,7 +2368,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
Bundle input = createTransactionWithPlaceholdersRequestBundle();
|
||||
Bundle output = createTransactionWithPlaceholdersResponseBundle();
|
||||
|
||||
ourReturn = Collections.singletonList((Resource) output);
|
||||
ourReturn = Collections.singletonList(output);
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||
httpPost.setEntity(createFhirResourceEntity(input));
|
||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||
|
@ -2111,7 +2401,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
Bundle input = createTransactionWithPlaceholdersRequestBundle();
|
||||
Bundle output = createTransactionWithPlaceholdersResponseBundle();
|
||||
|
||||
ourReturn = Collections.singletonList((Resource) output);
|
||||
ourReturn = Collections.singletonList(output);
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||
httpPost.setEntity(createFhirResourceEntity(input));
|
||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||
|
@ -2146,7 +2436,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
HttpPost httpPost;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Collections.singletonList((Resource) output);
|
||||
ourReturn = Collections.singletonList(output);
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||
httpPost.setEntity(createFhirResourceEntity(input));
|
||||
|
@ -2610,12 +2900,13 @@ public class AuthorizationInterceptorR4Test {
|
|||
DummyOrganizationResourceProvider orgProv = new DummyOrganizationResourceProvider();
|
||||
DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider();
|
||||
DummyCarePlanResourceProvider cpProv = new DummyCarePlanResourceProvider();
|
||||
DummyDiagnosticReportResourceProvider drProv = new DummyDiagnosticReportResourceProvider();
|
||||
PlainProvider plainProvider = new PlainProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
ourServlet = new RestfulServer(ourCtx);
|
||||
ourServlet.setFhirContext(ourCtx);
|
||||
ourServlet.setResourceProviders(patProvider, obsProv, encProv, cpProv, orgProv);
|
||||
ourServlet.setResourceProviders(patProvider, obsProv, encProv, cpProv, orgProv, drProv);
|
||||
ourServlet.setPlainProviders(plainProvider);
|
||||
ourServlet.setPagingProvider(new FifoMemoryPagingProvider(100));
|
||||
ServletHolder servletHolder = new ServletHolder(ourServlet);
|
||||
|
@ -2640,6 +2931,9 @@ public class AuthorizationInterceptorR4Test {
|
|||
@Read(version = true)
|
||||
public CarePlan read(@IdParam IdType theId) {
|
||||
ourHitMethod = true;
|
||||
if (ourReturn.isEmpty()) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
return (CarePlan) ourReturn.get(0);
|
||||
}
|
||||
|
||||
|
@ -2688,6 +2982,23 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
public static class DummyDiagnosticReportResourceProvider implements IResourceProvider {
|
||||
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return DiagnosticReport.class;
|
||||
}
|
||||
@Search()
|
||||
public List<Resource> search(
|
||||
@OptionalParam(name = "subject") ReferenceParam theSubject,
|
||||
@OptionalParam(name = "patient") ReferenceParam thePatient
|
||||
) {
|
||||
ourHitMethod = true;
|
||||
return ourReturn;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class DummyObservationResourceProvider implements IResourceProvider {
|
||||
|
||||
|
@ -2733,11 +3044,14 @@ public class AuthorizationInterceptorR4Test {
|
|||
@Read(version = true)
|
||||
public Observation read(@IdParam IdType theId) {
|
||||
ourHitMethod = true;
|
||||
if (ourReturn.isEmpty()) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
return (Observation) ourReturn.get(0);
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Resource> search() {
|
||||
public List<Resource> search(@OptionalParam(name = "subject") ReferenceParam theSubject) {
|
||||
ourHitMethod = true;
|
||||
return ourReturn;
|
||||
}
|
||||
|
@ -2764,6 +3078,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||
|
||||
@Create()
|
||||
|
@ -2861,11 +3176,14 @@ public class AuthorizationInterceptorR4Test {
|
|||
@Read(version = true)
|
||||
public Patient read(@IdParam IdType theId) {
|
||||
ourHitMethod = true;
|
||||
if (ourReturn.isEmpty()) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
return (Patient) ourReturn.get(0);
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Resource> search() {
|
||||
public List<Resource> search(@OptionalParam(name="_id") IdType theIdParam) {
|
||||
ourHitMethod = true;
|
||||
return ourReturn;
|
||||
}
|
||||
|
|
|
@ -1917,7 +1917,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
bundle.getNamedChildren("entry", entries);
|
||||
Element match = null;
|
||||
for (Element we : entries) {
|
||||
if (we.getChildValue("fullUrl").equals(targetUrl)) {
|
||||
if (targetUrl.equals(we.getChildValue("fullUrl"))) {
|
||||
Element r = we.getNamedChild("resource");
|
||||
if (version.isEmpty()) {
|
||||
rule(errors, IssueType.FORBIDDEN, -1, -1, path, match==null, "Multiple matches in bundle for reference " + ref);
|
||||
|
|
|
@ -317,6 +317,15 @@ public class FhirInstanceValidatorR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateBundleWithNoFullUrl() throws IOException {
|
||||
String encoded = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/r4/r4-caredove-bundle.json"));
|
||||
|
||||
ValidationResult output = myVal.validateWithResult(encoded);
|
||||
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
|
||||
assertEquals(44, errors.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64Valid() {
|
||||
Base64BinaryType value = new Base64BinaryType(new byte[] {2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1});
|
||||
|
|
|
@ -0,0 +1,815 @@
|
|||
{
|
||||
|
||||
"resourceType": "Bundle",
|
||||
|
||||
"type": "transaction",
|
||||
|
||||
"timestamp": "2018-03-09T15:21:51.2112Z",
|
||||
|
||||
"entry": [
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "ServiceRequest",
|
||||
|
||||
"id": 1,
|
||||
|
||||
"text": {
|
||||
|
||||
"status": "generated",
|
||||
|
||||
"div": "Human readable HTML narrative of entire ServiceRequest and related resources goes here. For brevity sake, it is omitted here, but can be included in an actual transmission"
|
||||
|
||||
},
|
||||
|
||||
"status": "active",
|
||||
|
||||
"intent": "proposal",
|
||||
|
||||
"priority": "routine",
|
||||
|
||||
"subject": {
|
||||
|
||||
"reference": "Patient/1"
|
||||
|
||||
},
|
||||
|
||||
"authoredOn": "2018-03-09T15:21:51Z",
|
||||
|
||||
"requester": {
|
||||
|
||||
"reference": "PractitionerRole/1"
|
||||
|
||||
},
|
||||
|
||||
"performer": {
|
||||
|
||||
"reference": "https://www.caredove.com/FHIR3/HealthcareService/8654"
|
||||
|
||||
},
|
||||
|
||||
"reasonCode": [
|
||||
|
||||
{
|
||||
|
||||
"text": "Reason for referral narrative goes here"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"supportingInfo": [
|
||||
|
||||
{
|
||||
|
||||
"reference": "DocumentReference/1"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"note": [
|
||||
|
||||
{
|
||||
|
||||
"text": "Allergies: Penicillin \nSocial History: History of family conflict \nlow social interaction \nFood Allergies: Peanuts"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "ServiceRequest"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "Patient",
|
||||
|
||||
"id": 1,
|
||||
|
||||
"identifier": [
|
||||
|
||||
{
|
||||
|
||||
"type": {
|
||||
|
||||
"coding": [
|
||||
|
||||
{
|
||||
|
||||
"code": "JHN",
|
||||
|
||||
"system": "http://hl7.org/fhir/v2/0203"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"text": "Ontario PHN"
|
||||
|
||||
},
|
||||
|
||||
"value": "4455 044 033",
|
||||
|
||||
"system": "http://ehealthontario.ca/API/FHIR/NamingSystem/ca-on-patient-hcn",
|
||||
|
||||
"extension": [
|
||||
|
||||
{
|
||||
|
||||
"url": "https://www.caredove.com/FHIR3/StructureDefinition/caredove-healthcardversion",
|
||||
|
||||
"valueString": "H"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"name": [
|
||||
|
||||
{
|
||||
|
||||
"given": [
|
||||
|
||||
"John",
|
||||
|
||||
"Scott"
|
||||
|
||||
],
|
||||
|
||||
"family": "Smith"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"telecom": [
|
||||
|
||||
{
|
||||
|
||||
"system": "phone",
|
||||
|
||||
"value": "(555) 111-1111",
|
||||
|
||||
"use": "mobile",
|
||||
|
||||
"rank": 1
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"system": "phone",
|
||||
|
||||
"value": "(555) 222-2222",
|
||||
|
||||
"rank": 2
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"system": "email",
|
||||
|
||||
"value": "testpatient@caredove.com"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"gender": "male",
|
||||
|
||||
"birthDate": "1928-06-29",
|
||||
|
||||
"address": [
|
||||
|
||||
{
|
||||
|
||||
"use": "home",
|
||||
|
||||
"type": "physical",
|
||||
|
||||
"line": [
|
||||
|
||||
"Unit 2",
|
||||
|
||||
"50 Albert St."
|
||||
|
||||
],
|
||||
|
||||
"city": "Waterloo",
|
||||
|
||||
"state": "ON",
|
||||
|
||||
"postalCode": "K8N 1N1",
|
||||
|
||||
"country": "Can"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"maritalStatus": {
|
||||
|
||||
"coding": [
|
||||
|
||||
{
|
||||
|
||||
"code": "M",
|
||||
|
||||
"display": "Married"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"text": "Married"
|
||||
|
||||
},
|
||||
|
||||
"contact": [
|
||||
|
||||
{
|
||||
|
||||
"relationship": [
|
||||
|
||||
{
|
||||
|
||||
"text": "Alternate Contact"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"name": {
|
||||
|
||||
"given": [
|
||||
|
||||
"Shemergency",
|
||||
|
||||
"Scott"
|
||||
|
||||
],
|
||||
|
||||
"family": "McContact"
|
||||
|
||||
},
|
||||
|
||||
"telecom": [
|
||||
|
||||
{
|
||||
|
||||
"system": "phone",
|
||||
|
||||
"value": "(555) 111-1111",
|
||||
|
||||
"use": "mobile",
|
||||
|
||||
"rank": 1
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"system": "phone",
|
||||
|
||||
"value": "(555) 222-2222",
|
||||
|
||||
"rank": 2
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"system": "email",
|
||||
|
||||
"value": "testcontact@caredove.com"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"address": {
|
||||
|
||||
"use": "home",
|
||||
|
||||
"type": "physical",
|
||||
|
||||
"line": [
|
||||
|
||||
"Unit 2",
|
||||
|
||||
"50 Albert St."
|
||||
|
||||
],
|
||||
|
||||
"city": "Waterloo",
|
||||
|
||||
"state": "ON",
|
||||
|
||||
"postalCode": "32819",
|
||||
|
||||
"country": "Can"
|
||||
|
||||
},
|
||||
|
||||
"gender": "female"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"communication": [
|
||||
|
||||
{
|
||||
|
||||
"language": {
|
||||
|
||||
"coding": [
|
||||
|
||||
{
|
||||
|
||||
"code": "en",
|
||||
|
||||
"display": "English"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"text": "English"
|
||||
|
||||
},
|
||||
|
||||
"preferred": true
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"generalPractitioner": [
|
||||
|
||||
{
|
||||
|
||||
"reference": "PractitionerRole/2"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "Patient"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "PractitionerRole",
|
||||
|
||||
"id": 1,
|
||||
|
||||
"practitioner": {
|
||||
|
||||
"reference": "Practitioner/1"
|
||||
|
||||
},
|
||||
|
||||
"organization": {
|
||||
|
||||
"reference": "Organization/1"
|
||||
|
||||
},
|
||||
|
||||
"location": [
|
||||
|
||||
{
|
||||
|
||||
"reference": "Location/1"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"telecom": [
|
||||
|
||||
{
|
||||
|
||||
"system": "phone",
|
||||
|
||||
"value": "(555) 111-1111",
|
||||
|
||||
"use": "work"
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"system": "email",
|
||||
|
||||
"value": "testsender@caredove.com",
|
||||
|
||||
"use": "work"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "PractitionerRole"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "Practitioner",
|
||||
|
||||
"id": 1,
|
||||
|
||||
"name": [
|
||||
|
||||
{
|
||||
|
||||
"given": [
|
||||
|
||||
"Requesty"
|
||||
|
||||
],
|
||||
|
||||
"family": "McSenderson"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "Practitioner"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "Organization",
|
||||
|
||||
"id": 1,
|
||||
|
||||
"name": "North Sender Clinic"
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "Organization"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "Location",
|
||||
|
||||
"id": 1,
|
||||
|
||||
"name": "Downtown Sender Hub",
|
||||
|
||||
"address": {
|
||||
|
||||
"use": "work",
|
||||
|
||||
"type": "physical",
|
||||
|
||||
"line": [
|
||||
|
||||
"Suite 11",
|
||||
|
||||
"11 King st. West"
|
||||
|
||||
],
|
||||
|
||||
"city": "Kitchener",
|
||||
|
||||
"state": "ON",
|
||||
|
||||
"postalCode": "N2L 1T1",
|
||||
|
||||
"country": "Can"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "Location"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "PractitionerRole",
|
||||
|
||||
"id": 2,
|
||||
|
||||
"practitioner": {
|
||||
|
||||
"reference": "Practitioner/2"
|
||||
|
||||
},
|
||||
|
||||
"organization": {
|
||||
|
||||
"reference": "Organization/2"
|
||||
|
||||
},
|
||||
|
||||
"location": [
|
||||
|
||||
{
|
||||
|
||||
"reference": "Location/2"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"telecom": [
|
||||
|
||||
{
|
||||
|
||||
"system": "phone",
|
||||
|
||||
"value": "(555) 222-2222",
|
||||
|
||||
"use": "work"
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"system": "email",
|
||||
|
||||
"value": "familydoc@caredove.com",
|
||||
|
||||
"use": "work"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "PractitionerRole"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "Practitioner",
|
||||
|
||||
"id": 2,
|
||||
|
||||
"name": [
|
||||
|
||||
{
|
||||
|
||||
"given": [
|
||||
|
||||
"Dr. Prim"
|
||||
|
||||
],
|
||||
|
||||
"family": "Caredoc"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "Practitioner"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "Organization",
|
||||
|
||||
"id": 2,
|
||||
|
||||
"name": "Star Family Health Team"
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "Organization"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "DocumentReference",
|
||||
|
||||
"id": 1,
|
||||
|
||||
"status": "current",
|
||||
|
||||
"created": "2018-03-09T15:21:51.2112Z",
|
||||
|
||||
"description": "Filename or Document Title goes here",
|
||||
|
||||
"content": [
|
||||
|
||||
{
|
||||
|
||||
"attachment": "NEEDS WORK - ATTACHMENT DATA TYPE",
|
||||
|
||||
"format": "NEEDS WORK - FORMAT INFO"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "Practitioner"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "Location",
|
||||
|
||||
"id": 2,
|
||||
|
||||
"name": "West Side GP Office",
|
||||
|
||||
"address": {
|
||||
|
||||
"use": "work",
|
||||
|
||||
"type": "physical",
|
||||
|
||||
"line": [
|
||||
|
||||
"22 Weber st. East"
|
||||
|
||||
],
|
||||
|
||||
"city": "Kitchener",
|
||||
|
||||
"state": "ON",
|
||||
|
||||
"postalCode": "N2L 2T2",
|
||||
|
||||
"country": "Can"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "Location"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"resource": {
|
||||
|
||||
"resourceType": "Task",
|
||||
|
||||
"id": 1,
|
||||
|
||||
"basedOn" : {
|
||||
|
||||
"reference" : "ServiceRequest/1"
|
||||
|
||||
},
|
||||
|
||||
"status" : "requested",
|
||||
|
||||
"businessStatus " : {
|
||||
|
||||
"text" : "Waiting for preliminary review"
|
||||
|
||||
},
|
||||
|
||||
"intent" : "proposal",
|
||||
|
||||
"code" : {
|
||||
|
||||
"text" : "Process Request"
|
||||
|
||||
},
|
||||
|
||||
"description" : "Process and close this referral request",
|
||||
|
||||
"authoredOn" : "2018-03-09T15:21:51Z",
|
||||
|
||||
"lastModified" : "2018-03-09T15:21:51Z"
|
||||
|
||||
},
|
||||
|
||||
"request": {
|
||||
|
||||
"method": "POST",
|
||||
|
||||
"url": "Task"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
}
|
|
@ -42,6 +42,30 @@
|
|||
Spring-data (used by the JPA server) has been upgraded to version 2.0.7
|
||||
(from version 1.11.6). Thanks to Roman Doboni for the pull request!
|
||||
</action>
|
||||
<action type="fix">
|
||||
A crash in the validator was fixed: Validating a Bundle that did not have fullUrl entries
|
||||
in a required spot caused a NullPointerException.
|
||||
</action>
|
||||
<action type="add">
|
||||
AuthorizationInterceptor now examines requests more closely in order
|
||||
to block requests early that are not possibly going to return
|
||||
allowable results when compartment rules are used. For example,
|
||||
if an AuthorizationInterceptor is configured to allow only
|
||||
<![CDATA[<b>read</b>]]>
|
||||
access to compartment
|
||||
<![CDATA[<code>Patient/123</code>]]>,
|
||||
a search for
|
||||
<![CDATA[<code>Observation?subject=987</code>]]>
|
||||
will now be blocked before the method handler is called. Previously
|
||||
the search was performed and the results were examined in order to
|
||||
determine whether they were all in the appropriate compartment, but
|
||||
this incurs a performance cost, and means that this search would
|
||||
successfully return an empty Bundle if no matches were present.
|
||||
<![CDATA[<br/><br/>]]>
|
||||
A new setting on AuthorizationInterceptor called
|
||||
<![CDATA[<code>setFlags(flags)</code>]]>
|
||||
can be used to maintain the previous behaviour.
|
||||
</action>
|
||||
</release>
|
||||
<release version="3.4.0" date="2018-05-28">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue