More work on terminology services, and add support to operations to AuthorizationInterceptor

This commit is contained in:
jamesagnew 2016-06-20 07:19:08 -04:00
parent 36505c60d8
commit 2e8c20dc83
28 changed files with 1052 additions and 387 deletions

View File

@ -194,7 +194,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
public void validateServerBaseIfConfiguredToDoSo(String theServerBase, IHttpClient theHttpClient, BaseClient theClient) {
String serverBase = normalizeBaseUrlForMap(theServerBase);
switch (myServerValidationMode) {
switch (getServerValidationMode()) {
case NEVER:
break;
case ONCE:
@ -267,22 +267,6 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
myPoolMaxPerRoute = thePoolMaxPerRoute;
resetHttpClient();
}
/**
* Instantiates a new client invocation handler
* @param theClient
* the client which will invoke the call
* @param theUrlBase
* the url base
* @param theMethodToReturnValue
* @param theBindings
* @param theMethodToLambda
* @return a newly created client invocation handler
*/
ClientInvocationHandler newInvocationHandler(IHttpClient theClient, String theUrlBase, Map<Method, Object> theMethodToReturnValue, Map<Method, BaseMethodBinding<?>> theBindings, Map<Method, ClientInvocationHandlerFactory.ILambda> theMethodToLambda) {
return new ClientInvocationHandler(theClient, getFhirContext(), theUrlBase.toString(), theMethodToReturnValue,
theBindings, theMethodToLambda, this);
}
@SuppressWarnings("unchecked")
@Override

View File

@ -340,19 +340,21 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append("<p>");
b.append("This result is being rendered in HTML for easy viewing. ");
b.append("You may view this content as ");
b.append("You may access this content as ");
b.append("<a href=\"");
b.append(createLinkHref(parameters, Constants.FORMAT_JSON));
b.append("\">Raw JSON</a>, ");
b.append("\">Raw JSON</a> or ");
b.append("<a href=\"");
b.append(createLinkHref(parameters, Constants.FORMAT_XML));
b.append("\">Raw XML</a>, ");
b.append(" or view this content in ");
b.append("<a href=\"");
b.append(createLinkHref(parameters, Constants.FORMATS_HTML_JSON));
b.append("\">HTML JSON</a>, ");
b.append("\">HTML JSON</a> ");
b.append("or ");
b.append("<a href=\"");

View File

@ -0,0 +1,30 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
abstract class BaseRule implements IAuthRule {
private String myName;
private PolicyEnum myMode;
BaseRule(String theRuleName) {
myName = theRuleName;
}
@Override
public String getName() {
return myName;
}
public void setMode(PolicyEnum theRuleMode) {
myMode = theRuleMode;
}
Verdict newVerdict() {
return new Verdict(myMode, this);
}
public PolicyEnum getMode() {
return myMode;
}
}

View File

@ -0,0 +1,17 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
public interface IAuthRuleBuilderOperation {
/**
* This rule applies to the operation with the given name
*
* @param The operation name, e.g. "validate" or "$validate" (either form may be used here)
*/
IAuthRuleBuilderOperationNamed named(String theOperationName);
/**
* This rule applies to any operation
*/
IAuthRuleBuilderOperationNamed withAnyName();
}

View File

@ -0,0 +1,23 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
public interface IAuthRuleBuilderOperationNamed {
/**
* Rule applies to invocations of this operation at the <code>server</code> level
*/
IAuthRuleBuilderRuleOpClassifierFinished onServer();
/**
* Rule applies to invocations of this operation at the <code>type</code> level
*/
IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType);
/**
* Rule applies to invocations of this operation at the <code>instance</code> level
*/
IAuthRuleBuilderRuleOpClassifierFinished onInstance(IIdType theInstanceId);
}

View File

@ -55,4 +55,9 @@ public interface IAuthRuleBuilderRule {
*/
IAuthRuleBuilderRuleOp write();
/**
* This rule applies to a FHIR operation (e.g. <code>$validate</code>)
*/
IAuthRuleBuilderOperation operation();
}

View File

@ -0,0 +1,96 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import java.util.HashSet;
import java.util.List;
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.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
class OperationRule extends BaseRule implements IAuthRule {
public OperationRule(String theRuleName) {
super(theRuleName);
}
private String myOperationName;
private boolean myAppliesToServer;
private HashSet<Class<? extends IBaseResource>> myAppliesToTypes;
private List<IIdType> myAppliesToIds;
/**
* Must include the leading $
*/
public void setOperationName(String theOperationName) {
myOperationName = theOperationName;
}
public String getOperationName() {
return myOperationName;
}
@Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
boolean applies = false;
switch (theOperation) {
case EXTENDED_OPERATION_SERVER:
if (myAppliesToServer) {
applies = true;
}
break;
case EXTENDED_OPERATION_TYPE:
if (myAppliesToTypes != null) {
for (Class<? extends IBaseResource> next : myAppliesToTypes) {
String resName = ctx.getResourceDefinition(theRequestDetails.getResourceName()).getName();
if (resName.equals(theRequestDetails.getResourceName())) {
applies = true;
break;
}
}
}
break;
case EXTENDED_OPERATION_INSTANCE:
if (myAppliesToIds != null) {
String instanceId = theRequestDetails.getId().toUnqualifiedVersionless().getValue();
for (IIdType next : myAppliesToIds) {
if (next.toUnqualifiedVersionless().getValue().equals(instanceId)) {
applies = true;
break;
}
}
}
break;
default:
return null;
}
if (!applies) {
return null;
}
if (myOperationName != null && !myOperationName.equals(theRequestDetails.getOperation())) {
return null;
}
return newVerdict();
}
public void appliesToServer() {
myAppliesToServer = true;
}
public void appliesToTypes(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
myAppliesToTypes = theAppliesToTypes;
}
public void appliesToInstances(List<IIdType> theAppliesToIds) {
myAppliesToIds = theAppliesToIds;
}
}

View File

@ -24,7 +24,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -40,20 +39,18 @@ import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
import ca.uhn.fhir.util.FhirTerser;
class Rule implements IAuthRule {
class Rule extends BaseRule implements IAuthRule {
private AppliesTypeEnum myAppliesTo;
private Set<?> myAppliesToTypes;
private String myClassifierCompartmentName;
private Collection<? extends IIdType> myClassifierCompartmentOwners;
private ClassifierTypeEnum myClassifierType;
private PolicyEnum myMode;
private String myName;
private RuleOpEnum myOp;
private TransactionAppliesToEnum myTransactionAppliesToOp;
public Rule(String theRuleName) {
myName = theRuleName;
super(theRuleName);
}
@Override
@ -77,7 +74,7 @@ class Rule implements IAuthRule {
case DELETE:
if (theOperation == RestOperationTypeEnum.DELETE) {
if (theInputResource == null) {
return new Verdict(myMode, this);
return newVerdict();
} else {
appliesTo = theInputResource;
}
@ -88,13 +85,13 @@ class Rule implements IAuthRule {
case BATCH:
case TRANSACTION:
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
if (myMode == PolicyEnum.DENY) {
if (getMode() == PolicyEnum.DENY) {
return new Verdict(PolicyEnum.DENY, this);
} else {
} else {
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) {
@ -111,13 +108,13 @@ class Rule implements IAuthRule {
/*
* 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.
* 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());
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null);
if (newVerdict == null) {
continue;
@ -155,7 +152,7 @@ class Rule implements IAuthRule {
return new Verdict(PolicyEnum.DENY, this);
case METADATA:
if (theOperation == RestOperationTypeEnum.METADATA) {
return new Verdict(myMode, this);
return newVerdict();
} else {
return null;
}
@ -196,14 +193,15 @@ class Rule implements IAuthRule {
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
}
return new Verdict(myMode, this);
return newVerdict();
}
private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) {
if (!"Bundle".equals(theContext.getResourceDefinition(theInputResource).getName())) {
return false;
}
IBaseBundle request = (IBaseBundle) theInputResource;
String bundleType = BundleUtil.getBundleType(theContext, request);
switch (theOp) {
@ -216,11 +214,6 @@ class Rule implements IAuthRule {
}
}
@Override
public String getName() {
return myName;
}
public TransactionAppliesToEnum getTransactionAppliesToOp() {
return myTransactionAppliesToOp;
}
@ -245,9 +238,6 @@ class Rule implements IAuthRule {
myClassifierType = theClassifierType;
}
public void setMode(PolicyEnum theRuleMode) {
myMode = theRuleMode;
}
public Rule setOp(RuleOpEnum theRuleOp) {
myOp = theRuleOp;
@ -257,4 +247,5 @@ class Rule implements IAuthRule {
public void setTransactionAppliesToOp(TransactionAppliesToEnum theOp) {
myTransactionAppliesToOp = theOp;
}
}

View File

@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -243,6 +244,80 @@ public class RuleBuilder implements IAuthRuleBuilder {
}
private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
private class RuleBuilderRuleOperationNamed implements IAuthRuleBuilderOperationNamed {
private String myOperationName;
public RuleBuilderRuleOperationNamed(String theOperationName) {
if (theOperationName != null && !theOperationName.startsWith("$")) {
myOperationName = '$' + theOperationName;
} else {
myOperationName = theOperationName;
}
}
@Override
public IAuthRuleBuilderRuleOpClassifierFinished onServer() {
OperationRule rule = createRule();
rule.appliesToServer();
myRules.add(rule);
return new RuleBuilderFinished();
}
private OperationRule createRule() {
OperationRule rule = new OperationRule(myRuleName);
rule.setOperationName(myOperationName);
rule.setMode(myRuleMode);
return rule;
}
@Override
public IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType) {
Validate.notNull(theType, "theType must not be null");
OperationRule rule = createRule();
HashSet<Class<? extends IBaseResource>> appliesToTypes = new HashSet<Class<? extends IBaseResource>>();
appliesToTypes.add(theType);
rule.appliesToTypes(appliesToTypes);
myRules.add(rule);
return new RuleBuilderFinished();
}
@Override
public IAuthRuleBuilderRuleOpClassifierFinished onInstance(IIdType theInstanceId) {
Validate.notNull(theInstanceId, "theInstanceId must not be null");
Validate.notBlank(theInstanceId.getResourceType(), "theInstanceId does not have a resource type");
Validate.notBlank(theInstanceId.getIdPart(), "theInstanceId does not have an ID part");
OperationRule rule = createRule();
ArrayList<IIdType> ids = new ArrayList<IIdType>();
ids.add(theInstanceId);
rule.appliesToInstances(ids);
myRules.add(rule);
return new RuleBuilderFinished();
}
}
@Override
public IAuthRuleBuilderOperationNamed named(String theOperationName) {
Validate.notBlank(theOperationName, "theOperationName must not be null or empty");
return new RuleBuilderRuleOperationNamed(theOperationName);
}
@Override
public IAuthRuleBuilderOperationNamed withAnyName() {
return new RuleBuilderRuleOperationNamed(null);
}
}
@Override
public IAuthRuleBuilderOperation operation() {
return new RuleBuilderRuleOperation();
}
}
}

View File

@ -28,5 +28,6 @@ enum RuleOpEnum {
TRANSACTION,
METADATA,
BATCH,
DELETE
DELETE,
OPERATION
}

View File

@ -1656,10 +1656,6 @@ public class XmlUtil {
ourHaveLoggedStaxImplementation = true;
}
public static void main(String[] args) throws FactoryConfigurationError, XMLStreamException {
createXmlWriter(new StringWriter());
}
private static final class ExtendedEntityReplacingXmlResolver implements XMLResolver {
@Override
public Object resolveEntity(String thePublicID, String theSystemID, String theBaseURI, String theNamespace) throws XMLStreamException {

View File

@ -26,7 +26,7 @@ public class FhirDbConfig {
extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
// extraProperties.put("hibernate.search.default.worker.execution", "async");
extraProperties.put("hibernate.search.default.worker.execution", "async");
if (System.getProperty("lowmem") != null) {
extraProperties.put("hibernate.search.autoregister_listeners", "false");

View File

@ -51,7 +51,7 @@ public class DaoConfig {
// ***
// update setter javadoc if default changes
// ***
private int myDeferIndexingForCodesystemsOfSize = 100;
private int myDeferIndexingForCodesystemsOfSize = 2000;
// ***
// update setter javadoc if default changes
// ***
@ -90,7 +90,7 @@ public class DaoConfig {
* the code system will be indexed later in an incremental process in order to
* avoid overwhelming Lucene with a huge number of codes in a single operation.
* <p>
* Defaults to 100
* Defaults to 2000
* </p>
*/
public int getDeferIndexingForCodesystemsOfSize() {
@ -273,7 +273,7 @@ public class DaoConfig {
* the code system will be indexed later in an incremental process in order to
* avoid overwhelming Lucene with a huge number of codes in a single operation.
* <p>
* Defaults to 100
* Defaults to 2000
* </p>
*/
public void setDeferIndexingForCodesystemsOfSize(int theDeferIndexingForCodesystemsOfSize) {

View File

@ -55,14 +55,12 @@ import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.indexes.interceptor.DontInterceptEntityInterceptor;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor;
//@formatter:off
@Entity
@Indexed(interceptor=DeferConceptIndexingInterceptor.class)
@Indexed()
@Table(name="TRM_CONCEPT", uniqueConstraints= {
@UniqueConstraint(name="IDX_CONCEPT_CS_CODE", columnNames= {"CODESYSTEM_PID", "CODE"})
}, indexes= {

View File

@ -66,6 +66,10 @@ public class TermConceptParentChildLink implements Serializable {
return myChild;
}
public RelationshipTypeEnum getRelationshipType() {
return myRelationshipType;
}
public TermCodeSystemVersion getCodeSystem() {
return myCodeSystem;
}

View File

@ -26,7 +26,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@ -85,7 +84,9 @@ public class TerminologyUploaderProviderDstu3 extends BaseJpaProvider {
} else if (thePackage == null || thePackage.getData() == null || thePackage.getData().length == 0) {
throw new InvalidRequestException("No 'localfile' or 'package' parameter, or package had no data");
} else {
data = Arrays.asList(thePackage.getData());
data = new ArrayList<byte[]>();
data.add(thePackage.getData());
thePackage.setData(null);
}
String url = theUrl != null ? theUrl.getValueAsString() : null;

View File

@ -1,32 +0,0 @@
package ca.uhn.fhir.jpa.search;
import org.hibernate.search.indexes.interceptor.EntityIndexingInterceptor;
import org.hibernate.search.indexes.interceptor.IndexingOverride;
import ca.uhn.fhir.jpa.entity.TermConcept;
public class DeferConceptIndexingInterceptor implements EntityIndexingInterceptor<TermConcept> {
@Override
public IndexingOverride onAdd(TermConcept theEntity) {
if (theEntity.getIndexStatus() == null) {
return IndexingOverride.SKIP;
}
return IndexingOverride.APPLY_DEFAULT;
}
@Override
public IndexingOverride onUpdate(TermConcept theEntity) {
return onAdd(theEntity);
}
@Override
public IndexingOverride onDelete(TermConcept theEntity) {
return IndexingOverride.APPLY_DEFAULT;
}
@Override
public IndexingOverride onCollectionUpdate(TermConcept theEntity) {
return IndexingOverride.APPLY_DEFAULT;
}
}

View File

@ -33,6 +33,7 @@ import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@ -67,9 +68,13 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
@Autowired
protected ITermConceptDao myConceptDao;
private List<TermConceptParentChildLink> myConceptLinksToSaveLater = new ArrayList<TermConceptParentChildLink>();
@Autowired
private ITermConceptParentChildLinkDao myConceptParentChildLinkDao;
private List<TermConcept> myConceptsToSaveLater = new ArrayList<TermConcept>();
@Autowired
protected FhirContext myContext;
@ -78,6 +83,8 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
private boolean myProcessDeferred = true;
private boolean addToSet(Set<TermConcept> theSetToPopulate, TermConcept theConcept) {
boolean retVal = theSetToPopulate.add(theConcept);
@ -197,37 +204,101 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
TermCodeSystemVersion csv = cs.getCurrentVersion();
return csv;
}
private TermCodeSystem getCodeSystem(String theSystem) {
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theSystem);
return cs;
}
private void parentPids(TermConcept theNextConcept, Set<Long> theParentPids) {
for (TermConceptParentChildLink nextParentLink : theNextConcept.getParents()){
TermConcept parent = nextParentLink.getParent();
if (parent != null && theParentPids.add(parent.getId())) {
parentPids(parent, theParentPids);
}
}
}
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) {
if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) {
return;
}
if (theConceptsStack.size() % 1000 == 0) {
if (theConceptsStack.size() == 1 || theConceptsStack.size() % 10000 == 0) {
float pct = (float) theConceptsStack.size() / (float) theTotalConcepts;
ourLog.info("Have saved {}/{} concepts ({}%), flushing", theConceptsStack.size(), theTotalConcepts, (int)( pct*100.0f));
myConceptDao.flush();
myConceptParentChildLinkDao.flush();
ourLog.info("Have processed {}/{} concepts ({}%)", theConceptsStack.size(), theTotalConcepts, (int)( pct*100.0f));
}
theConcept.setCodeSystem(theCodeSystem);
if (theTotalConcepts < myDaoConfig.getDeferIndexingForCodesystemsOfSize()) {
theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED);
}
theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED);
myConceptDao.save(theConcept);
Set<Long> parentPids = new HashSet<Long>();
parentPids(theConcept, parentPids);
theConcept.setParentPids(parentPids);
if (theConceptsStack.size() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) {
myConceptDao.save(theConcept);
} else {
myConceptsToSaveLater.add(theConcept);
}
for (TermConceptParentChildLink next : theConcept.getChildren()) {
persistChildren(next.getChild(), theCodeSystem, theConceptsStack, theTotalConcepts);
}
for (TermConceptParentChildLink next : theConcept.getChildren()) {
myConceptParentChildLinkDao.save(next);
if (theConceptsStack.size() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) {
myConceptParentChildLinkDao.save(next);
} else {
myConceptLinksToSaveLater.add(next);
}
}
}
private void populateVersion(TermConcept theNext, TermCodeSystemVersion theCodeSystemVersion) {
if (theNext.getCodeSystem() != null) {
return;
}
theNext.setCodeSystem(theCodeSystemVersion);
for (TermConceptParentChildLink next : theNext.getChildren()) {
populateVersion(next.getChild(), theCodeSystemVersion);
}
}
@Scheduled(fixedRate=5000)
@Transactional(propagation=Propagation.REQUIRED)
@Override
public synchronized void saveDeferred() {
if (!myProcessDeferred || ((myConceptsToSaveLater.isEmpty() && myConceptLinksToSaveLater.isEmpty()))) {
return;
}
int codeCount = 0, relCount = 0;
int count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myConceptsToSaveLater.size());
ourLog.info("Saving {} deferred concepts...", count);
while (codeCount < count && myConceptsToSaveLater.size() > 0) {
TermConcept next = myConceptsToSaveLater.remove(0);
myConceptDao.save(next);
codeCount++;
}
if (codeCount == 0) {
count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myConceptLinksToSaveLater.size());
ourLog.info("Saving {} deferred concept relationships...", count);
while (relCount < count && myConceptLinksToSaveLater.size() > 0) {
TermConceptParentChildLink next = myConceptLinksToSaveLater.remove(0);
myConceptParentChildLinkDao.save(next);
relCount++;
}
}
ourLog.info("Saved {} deferred concepts ({} remain) and {} deferred relationships ({} remain)", new Object[] {codeCount, myConceptsToSaveLater.size(), relCount, myConceptLinksToSaveLater.size()});
}
@Override
public void setProcessDeferred(boolean theProcessDeferred) {
myProcessDeferred = theProcessDeferred;
}
@Override
@ -265,7 +336,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
ourLog.info("Validating all codes in CodeSystem for storage (this can take some time for large sets)");
// Validate the code system
IdentityHashMap<TermConcept, Object> conceptsStack = new IdentityHashMap<TermConcept, Object>();
ArrayList<String> conceptsStack = new ArrayList<String>();
IdentityHashMap<TermConcept, Object> allConcepts = new IdentityHashMap<TermConcept, Object>();
int totalCodeCount = 0;
for (TermConcept next : theCodeSystemVersion.getConcepts()) {
@ -281,11 +352,17 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
codeSystem.setCurrentVersion(theCodeSystemVersion);
codeSystem = myCodeSystemDao.saveAndFlush(codeSystem);
ourLog.info("Saving {} concepts...", totalCodeCount);
ourLog.info("Setting codesystemversion on {} concepts...", totalCodeCount);
conceptsStack = new IdentityHashMap<TermConcept, Object>();
for (TermConcept next : theCodeSystemVersion.getConcepts()) {
persistChildren(next, codeSystemVersion, conceptsStack, totalCodeCount);
populateVersion(next, codeSystemVersion);
}
ourLog.info("Saving {} concepts...", totalCodeCount);
IdentityHashMap<TermConcept, Object> conceptsStack2 = new IdentityHashMap<TermConcept, Object>();
for (TermConcept next : theCodeSystemVersion.getConcepts()) {
persistChildren(next, codeSystemVersion, conceptsStack2, totalCodeCount);
}
ourLog.info("Done saving concepts, flushing to database");
@ -293,27 +370,6 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
myConceptDao.flush();
myConceptParentChildLinkDao.flush();
ourLog.info("Building multi-axial hierarchy...");
int index = 0;
int totalParents = 0;
for (TermConcept nextConcept : conceptsStack.keySet()) {
if (index++ % 1000 == 0) {
float pct = (float) index / (float) totalCodeCount;
ourLog.info("Have built hierarchy for {}/{} concepts - {}%", index, totalCodeCount, (int)( pct*100.0f));
}
Set<Long> parentPids = new HashSet<Long>();
parentPids(nextConcept, parentPids);
nextConcept.setParentPids(parentPids);
totalParents += parentPids.size();
myConceptDao.save(nextConcept);
}
ourLog.info("Done building hierarchy, found {} parents", totalParents);
/*
* For now we always delete old versions.. At some point it would be nice to allow configuration to keep old versions
*/
@ -326,17 +382,12 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
}
ourLog.info("Done deleting old code system versions");
}
private void parentPids(TermConcept theNextConcept, Set<Long> theParentPids) {
for (TermConceptParentChildLink nextParentLink : theNextConcept.getParents()){
TermConcept parent = nextParentLink.getParent();
if (parent != null && theParentPids.add(parent.getId())) {
parentPids(parent, theParentPids);
}
if (myConceptsToSaveLater.size() > 0 || myConceptLinksToSaveLater.size() > 0) {
ourLog.info("Note that some concept saving was deferred - still have {} concepts and {} relationships", myConceptsToSaveLater.size(), myConceptLinksToSaveLater.size());
}
}
@Override
public boolean supportsSystem(String theSystem) {
TermCodeSystem cs = getCodeSystem(theSystem);
@ -351,16 +402,17 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
return retVal;
}
private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack,
private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, ArrayList<String> theConceptsStack,
IdentityHashMap<TermConcept, Object> theAllConcepts) {
ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystem() != null, "CodesystemValue is null");
ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystem() == theCodeSystem, "CodeSystems are not equal");
ValidateUtil.isNotBlankOrThrowInvalidRequest(theConcept.getCode(), "Codesystem contains a code with no code value");
if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) {
if (theConceptsStack.contains(theConcept.getCode())) {
throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode());
}
theConceptsStack.add(theConcept.getCode());
int retVal = 0;
if (theAllConcepts.put(theConcept, theAllConcepts) == null) {
if (theAllConcepts.size() % 1000 == 0) {
@ -374,7 +426,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
retVal += validateConceptForStorage(next.getChild(), theCodeSystem, theConceptsStack, theAllConcepts);
}
theConceptsStack.remove(theConcept);
theConceptsStack.remove(theConceptsStack.size() - 1);
return retVal;
}

View File

@ -48,4 +48,12 @@ public interface IHapiTerminologySvc {
List<TermConcept> findCodes(String theSystem);
void saveDeferred();
/**
* This is mostly for unit tests - we can disable processing of deferred concepts
* by changing this flag
*/
void setProcessDeferred(boolean theProcessDeferred);
}

View File

@ -32,6 +32,7 @@ import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -262,11 +263,18 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
ourLog.info("Have {} total concepts, {} root concepts", code2concept.size(), codeSystemVersion.getConcepts().size());
myTermSvc.storeNewCodeSystemVersion(LOINC_URL, codeSystemVersion, theRequestDetails);
String url = LOINC_URL;
storeCodeSystem(theRequestDetails, codeSystemVersion, url);
return new UploadStatistics(code2concept.size());
}
private void storeCodeSystem(RequestDetails theRequestDetails, final TermCodeSystemVersion codeSystemVersion, String url) {
myTermSvc.setProcessDeferred(false);
myTermSvc.storeNewCodeSystemVersion(url, codeSystemVersion, theRequestDetails);
myTermSvc.setProcessDeferred(true);
}
UploadStatistics processSnomedCtFiles(List<byte[]> theZipBytes, RequestDetails theRequestDetails) {
final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion();
final Map<String, TermConcept> id2concept = new HashMap<String, TermConcept>();
@ -289,6 +297,13 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
theZipBytes.clear();
ourLog.info("Looking for root codes");
for (Iterator<Entry<String, TermConcept>> iter = rootConcepts.entrySet().iterator(); iter.hasNext(); ) {
if (iter.next().getValue().getParents().isEmpty() == false) {
iter.remove();
}
}
ourLog.info("Done loading SNOMED CT files - {} root codes, {} total codes", rootConcepts.size(), code2concept.size());
Counter circularCounter = new Counter();
@ -300,7 +315,8 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
}
codeSystemVersion.getConcepts().addAll(rootConcepts.values());
myTermSvc.storeNewCodeSystemVersion(SCT_URL, codeSystemVersion, theRequestDetails);
String url = SCT_URL;
storeCodeSystem(theRequestDetails, codeSystemVersion, url);
return new UploadStatistics(code2concept.size());
}
@ -470,23 +486,33 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
String destinationId = theRecord.get("destinationId");
String typeId = theRecord.get("typeId");
boolean active = "1".equals(theRecord.get("active"));
if (!active) {
return;
}
TermConcept typeConcept = myCode2concept.get(typeId);
TermConcept sourceConcept = myCode2concept.get(sourceId);
TermConcept targetConcept = myCode2concept.get(destinationId);
if (sourceConcept != null && targetConcept != null && typeConcept != null) {
if (typeConcept.getDisplay().equals("Is a (attribute)")) {
RelationshipTypeEnum relationshipType = RelationshipTypeEnum.ISA;
if (!sourceId.equals(destinationId)) {
TermConceptParentChildLink link = new TermConceptParentChildLink();
link.setChild(sourceConcept);
link.setParent(targetConcept);
link.setRelationshipType(TermConceptParentChildLink.RelationshipTypeEnum.ISA);
link.setCodeSystem(myCodeSystemVersion);
myRootConcepts.remove(link.getChild().getCode());
targetConcept.addChild(sourceConcept, RelationshipTypeEnum.ISA);
if (active) {
TermConceptParentChildLink link = new TermConceptParentChildLink();
link.setChild(sourceConcept);
link.setParent(targetConcept);
link.setRelationshipType(relationshipType);
link.setCodeSystem(myCodeSystemVersion);
targetConcept.addChild(sourceConcept, relationshipType);
} else {
// not active, so we're removing any existing links
for (TermConceptParentChildLink next : new ArrayList<TermConceptParentChildLink>(targetConcept.getChildren())) {
if (next.getRelationshipType() == relationshipType) {
if (next.getChild().getCode().equals(sourceConcept.getCode())) {
next.getParent().getChildren().remove(next);
next.getChild().getParents().remove(next);
}
}
}
}
}
} else if (ignoredTypes.contains(typeConcept.getDisplay())) {
// ignore

View File

@ -344,6 +344,8 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
public void testIndexingIsDeferredForLargeCodeSystems() {
myDaoConfig.setDeferIndexingForCodesystemsOfSize(1);
myTermSvc.setProcessDeferred(false);
createExternalCsAndLocalVs();
ValueSet vs = new ValueSet();
@ -353,10 +355,25 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
include.addFilter().setProperty("display").setOp(FilterOperator.ISA).setValue("ParentA");
ValueSet result = myValueSetDao.expand(vs, null);
String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result);
ourLog.info(encoded);
assertEquals(0, result.getExpansion().getContains().size());
myTermSvc.setProcessDeferred(true);
myTermSvc.saveDeferred();
myTermSvc.saveDeferred();
myTermSvc.saveDeferred();
myTermSvc.saveDeferred();
myTermSvc.saveDeferred();
myTermSvc.saveDeferred();
myTermSvc.saveDeferred();
result = myValueSetDao.expand(vs, null);
encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result);
ourLog.info(encoded);
assertEquals(4, result.getExpansion().getContains().size());
}
@Test

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.term;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInRelativeOrder;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@ -12,8 +13,10 @@ import static org.mockito.Mockito.verify;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@ -40,19 +43,19 @@ import ca.uhn.fhir.util.TestUtil;
public class TerminologyLoaderSvcTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvcTest.class);
private TerminologyLoaderSvc mySvc;
@Mock
private IHapiTerminologySvc myTermSvc;
@Captor
private ArgumentCaptor<TermCodeSystemVersion> myCsvCaptor;
@Before
public void before() {
mySvc = new TerminologyLoaderSvc();
mySvc.setTermSvcForUnitTests(myTermSvc);
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
@ -62,18 +65,18 @@ public class TerminologyLoaderSvcTest {
public void testLoadLoinc() throws Exception {
ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
ZipOutputStream zos1 = new ZipOutputStream(bos1);
addEntry(zos1,"/loinc/", "loinc.csv");
addEntry(zos1, "/loinc/", "loinc.csv");
zos1.close();
ourLog.info("ZIP file has {} bytes", bos1.toByteArray().length);
ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
ZipOutputStream zos2 = new ZipOutputStream(bos2);
addEntry(zos2,"/loinc/", "LOINC_2.54_MULTI-AXIAL_HIERARCHY.CSV");
addEntry(zos2, "/loinc/", "LOINC_2.54_MULTI-AXIAL_HIERARCHY.CSV");
zos2.close();
ourLog.info("ZIP file has {} bytes", bos2.toByteArray().length);
RequestDetails details = mock(RequestDetails.class);
mySvc.loadLoinc(Arrays.asList(bos1.toByteArray(), bos2.toByteArray()), details);
mySvc.loadLoinc(list(bos1.toByteArray(), bos2.toByteArray()), details);
}
@Test
@ -84,38 +87,48 @@ public class TerminologyLoaderSvcTest {
addEntry(zos, "/sct/", "sct2_Concept_Full-en_INT_20160131.txt");
addEntry(zos, "/sct/", "sct2_Description_Full-en_INT_20160131.txt");
addEntry(zos, "/sct/", "sct2_Identifier_Full_INT_20160131.txt");
addEntry(zos,"/sct/", "sct2_Relationship_Full_INT_20160131.txt");
addEntry(zos,"/sct/", "sct2_StatedRelationship_Full_INT_20160131.txt");
addEntry(zos, "/sct/", "sct2_Relationship_Full_INT_20160131.txt");
addEntry(zos, "/sct/", "sct2_StatedRelationship_Full_INT_20160131.txt");
addEntry(zos, "/sct/", "sct2_TextDefinition_Full-en_INT_20160131.txt");
zos.close();
ourLog.info("ZIP file has {} bytes", bos.toByteArray().length);
RequestDetails details = mock(RequestDetails.class);
mySvc.loadSnomedCt(Collections.singletonList(bos.toByteArray()), details);
mySvc.loadSnomedCt(list(bos.toByteArray()), details);
verify(myTermSvc).storeNewCodeSystemVersion(any(String.class), myCsvCaptor.capture(), any(RequestDetails.class));
TermCodeSystemVersion csv = myCsvCaptor.getValue();
TreeSet<String> allCodes = toCodes(csv);
TreeSet<String> allCodes = toCodes(csv, true);
ourLog.info(allCodes.toString());
assertThat(allCodes, containsInRelativeOrder("116680003"));
assertThat(allCodes, not(containsInRelativeOrder("207527008")));
allCodes = toCodes(csv, false);
ourLog.info(allCodes.toString());
assertThat(allCodes, hasItem("126816002"));
}
private TreeSet<String> toCodes(TermCodeSystemVersion theCsv) {
private List<byte[]> list(byte[]... theByteArray) {
return new ArrayList<byte[]>(Arrays.asList(theByteArray));
}
private TreeSet<String> toCodes(TermCodeSystemVersion theCsv, boolean theAddChildren) {
TreeSet<String> retVal = new TreeSet<String>();
for (TermConcept next : theCsv.getConcepts()) {
toCodes(retVal, next);
toCodes(retVal, next, theAddChildren);
}
return retVal;
}
private void toCodes(TreeSet<String> theCodes, TermConcept theConcept) {
private void toCodes(TreeSet<String> theCodes, TermConcept theConcept, boolean theAddChildren) {
theCodes.add(theConcept.getCode());
for (TermConceptParentChildLink next : theConcept.getChildren()) {
toCodes(theCodes, next.getChild());
if (theAddChildren) {
for (TermConceptParentChildLink next : theConcept.getChildren()) {
toCodes(theCodes, next.getChild(), theAddChildren);
}
}
}
@ -125,9 +138,9 @@ public class TerminologyLoaderSvcTest {
ZipOutputStream zos = new ZipOutputStream(bos);
addEntry(zos, "/sct/", "sct2_StatedRelationship_Full_INT_20160131.txt");
zos.close();
ourLog.info("ZIP file has {} bytes", bos.toByteArray().length);
RequestDetails details = mock(RequestDetails.class);
try {
mySvc.loadSnomedCt(Collections.singletonList(bos.toByteArray()), details);
@ -145,6 +158,5 @@ public class TerminologyLoaderSvcTest {
zos.write(byteArray);
zos.closeEntry();
}
}

View File

@ -13,6 +13,7 @@ id effectiveTime active moduleId definitionStatusId
126813005 20020131 1 900000000000207008 900000000000074008
126813006 20020131 1 900000000000207008 900000000000074008
126817006 20020131 1 900000000000207008 900000000000074008
126816002 20020131 1 900000000000207008 900000000000074008
207527008 20020131 1 900000000000207008 900000000000074008
207527008 20040731 1 900000000000207008 900000000000073002
207527008 20090731 0 900000000000207008 900000000000074008

View File

@ -1,5 +1,6 @@
id effectiveTime active moduleId sourceId destinationId relationshipGroup typeId characteristicTypeId modifierId
100022 20020131 1 900000000000207008 126815003 126813005 0 116680003 900000000000011006 900000000000451002
100022 20090731 0 900000000000207008 126816002 126813005 0 116680003 900000000000011006 900000000000451002
100025 20020131 1 900000000000207008 126816002 126813005 0 116680003 900000000000011006 900000000000451002
100025 20090731 0 900000000000207008 126816002 126813005 0 116680003 900000000000011006 900000000000451002
101021 20020131 1 900000000000207008 126817006 126815003 0 116680003 900000000000011006 900000000000451002
101021 20020131 1 900000000000207008 126815003 126817006 0 116680003 900000000000011006 900000000000451002

View File

@ -46,6 +46,7 @@ import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.ExtensionDt;
@ -77,6 +78,8 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.method.SearchStyleEnum;
@ -88,42 +91,24 @@ import ca.uhn.fhir.util.TestUtil;
public class GenericClientDstu2Test {
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientDstu2Test.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientDstu2Test.class);
private HttpClient myHttpClient;
private HttpResponse myHttpResponse;
private int myResponseCount = 0;
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testReadForUnknownType() throws Exception {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
try {
client.read(new UriDt("1"));
fail();
} catch (IllegalArgumentException e) {
assertEquals("The given URI is not an absolute URL and is not usable for this operation: 1", e.getMessage());
}
try {
client.read(new UriDt("http://example.com/InvalidResource/1"));
fail();
} catch (DataFormatException e) {
assertEquals("Unknown resource name \"InvalidResource\" (this name is not known in FHIR version \"DSTU2\")", e.getMessage());
}
}
@Before
public void before() {
myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
ourCtx.setRestfulClientFactory(new ApacheRestfulClientFactory(ourCtx));
ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
ourCtx.getRestfulClientFactory().setConnectionRequestTimeout(10000);
ourCtx.getRestfulClientFactory().setConnectTimeout(10000);
ourCtx.getRestfulClientFactory().setPoolMaxPerRoute(100);
ourCtx.getRestfulClientFactory().setPoolMaxTotal(100);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
myResponseCount = 0;
@ -156,7 +141,7 @@ public class GenericClientDstu2Test {
//@formatter:on
return msg;
}
@Test
public void testAcceptHeaderFetchConformance() throws Exception {
IParser p = ourCtx.newXmlParser();
@ -199,68 +184,6 @@ public class GenericClientDstu2Test {
idx++;
}
/**
* See #322
*/
@Test
public void testFetchConformanceWithSmartExtensions() throws Exception {
final String respString = IOUtils.toString(GenericClientDstu2Test.class.getResourceAsStream("/conformance_322.json"));
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:8080/fhir");
Conformance conf = client.fetchConformance().ofType(Conformance.class).execute();
Rest rest = conf.getRest().get(0);
RestSecurity security = rest.getSecurity();
List<ExtensionDt> ext = security.getUndeclaredExtensionsByUrl("http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris");
List<ExtensionDt> tokenExts = ext.get(0).getUndeclaredExtensionsByUrl("token");
ExtensionDt tokenExt = tokenExts.get(0);
UriDt value = (UriDt) tokenExt.getValue();
assertEquals("https://my-server.org/token", value.getValueAsString());
}
/**
* See #322
*/
@Test
public void testFetchConformanceWithSmartExtensionsAltCase() throws Exception {
final String respString = IOUtils.toString(GenericClientDstu2Test.class.getResourceAsStream("/conformance_322.json")).replace("valueuri", "valueUri");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:8080/fhir");
Conformance conf = client.fetchConformance().ofType(Conformance.class).execute();
Rest rest = conf.getRest().get(0);
RestSecurity security = rest.getSecurity();
List<ExtensionDt> ext = security.getUndeclaredExtensionsByUrl("http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris");
List<ExtensionDt> tokenExts = ext.get(0).getUndeclaredExtensionsByUrl("token");
ExtensionDt tokenExt = tokenExts.get(0);
UriDt value = (UriDt) tokenExt.getValue();
assertEquals("https://my-server.org/token", value.getValueAsString());
}
@Test
public void testAcceptHeaderPreflightConformance() throws Exception {
String methodName = "testAcceptHeaderPreflightConformance";
@ -560,6 +483,30 @@ public class GenericClientDstu2Test {
assertEquals("Patient/123", output.getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
@Test
public void testDeleteByResource() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_204_NO_CONTENT, ""));
when(myHttpResponse.getEntity().getContent()).then(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
Patient pat = new Patient();
pat.setId("Patient/123");
client.delete().resource(pat).execute();
assertEquals("DELETE", capt.getAllValues().get(idx).getMethod());
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(idx).getURI().toString());
}
@Test
public void testDeleteConditional() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@ -595,59 +542,6 @@ public class GenericClientDstu2Test {
}
@SuppressWarnings("deprecation")
@Test
public void testDeleteNonFluent() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_204_NO_CONTENT, ""));
when(myHttpResponse.getEntity().getContent()).then(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
client.delete(Patient.class, new IdDt("Patient/123"));
assertEquals("DELETE", capt.getAllValues().get(idx).getMethod());
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(idx).getURI().toString());
idx++;
client.delete(Patient.class, "123");
assertEquals("DELETE", capt.getAllValues().get(idx).getMethod());
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(idx).getURI().toString());
idx++;
}
@Test
public void testDeleteByResource() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_204_NO_CONTENT, ""));
when(myHttpResponse.getEntity().getContent()).then(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
Patient pat = new Patient();
pat.setId("Patient/123");
client.delete().resource(pat).execute();
assertEquals("DELETE", capt.getAllValues().get(idx).getMethod());
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(idx).getURI().toString());
}
@Test
public void testDeleteInvalidRequest() throws Exception {
Patient pat = new Patient();
@ -691,6 +585,97 @@ public class GenericClientDstu2Test {
}
}
@SuppressWarnings("deprecation")
@Test
public void testDeleteNonFluent() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_204_NO_CONTENT, ""));
when(myHttpResponse.getEntity().getContent()).then(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
client.delete(Patient.class, new IdDt("Patient/123"));
assertEquals("DELETE", capt.getAllValues().get(idx).getMethod());
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(idx).getURI().toString());
idx++;
client.delete(Patient.class, "123");
assertEquals("DELETE", capt.getAllValues().get(idx).getMethod());
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(idx).getURI().toString());
idx++;
}
/**
* See #322
*/
@Test
public void testFetchConformanceWithSmartExtensions() throws Exception {
final String respString = IOUtils.toString(GenericClientDstu2Test.class.getResourceAsStream("/conformance_322.json"));
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:8080/fhir");
Conformance conf = client.fetchConformance().ofType(Conformance.class).execute();
Rest rest = conf.getRest().get(0);
RestSecurity security = rest.getSecurity();
List<ExtensionDt> ext = security.getUndeclaredExtensionsByUrl("http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris");
List<ExtensionDt> tokenExts = ext.get(0).getUndeclaredExtensionsByUrl("token");
ExtensionDt tokenExt = tokenExts.get(0);
UriDt value = (UriDt) tokenExt.getValue();
assertEquals("https://my-server.org/token", value.getValueAsString());
}
/**
* See #322
*/
@Test
public void testFetchConformanceWithSmartExtensionsAltCase() throws Exception {
final String respString = IOUtils.toString(GenericClientDstu2Test.class.getResourceAsStream("/conformance_322.json")).replace("valueuri", "valueUri");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:8080/fhir");
Conformance conf = client.fetchConformance().ofType(Conformance.class).execute();
Rest rest = conf.getRest().get(0);
RestSecurity security = rest.getSecurity();
List<ExtensionDt> ext = security.getUndeclaredExtensionsByUrl("http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris");
List<ExtensionDt> tokenExts = ext.get(0).getUndeclaredExtensionsByUrl("token");
ExtensionDt tokenExt = tokenExts.get(0);
UriDt value = (UriDt) tokenExt.getValue();
assertEquals("https://my-server.org/token", value.getValueAsString());
}
@Test
public void testHistory() throws Exception {
@ -796,6 +781,16 @@ public class GenericClientDstu2Test {
idx++;
}
@Test
public void testInvalidClient() {
try {
ourCtx.getRestfulClientFactory().newClient(RestfulClientInstance.class, "http://foo");
fail();
} catch (ConfigurationException e) {
assertEquals("ca.uhn.fhir.context.ConfigurationException: ca.uhn.fhir.rest.client.GenericClientDstu2Test.RestfulClientInstance is not an interface", e.toString());
}
}
@Test
public void testMetaAdd() throws Exception {
IParser p = ourCtx.newXmlParser();
@ -1663,6 +1658,24 @@ public class GenericClientDstu2Test {
assertEquals("FAM", response.getName().get(0).getFamily().get(0).getValue());
}
@Test
public void testReadForUnknownType() throws Exception {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
try {
client.read(new UriDt("1"));
fail();
} catch (IllegalArgumentException e) {
assertEquals("The given URI is not an absolute URL and is not usable for this operation: 1", e.getMessage());
}
try {
client.read(new UriDt("http://example.com/InvalidResource/1"));
fail();
} catch (DataFormatException e) {
assertEquals("Unknown resource name \"InvalidResource\" (this name is not known in FHIR version \"DSTU2\")", e.getMessage());
}
}
@Test
public void testReadUpdatedHeaderDoesntOverwriteResourceValue() throws Exception {
@ -1786,6 +1799,76 @@ public class GenericClientDstu2Test {
}
@Test
public void testSearchByNumber() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.greaterThan().number(123))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=gt123", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.lessThan().number(123))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=lt123", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.greaterThanOrEqual().number("123"))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=ge123", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.lessThanOrEqual().number("123"))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=le123", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.exactly().number(123))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=123", capt.getAllValues().get(idx).getURI().toString());
idx++;
}
@Test
public void testSearchByPost() throws Exception {
String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
@ -1892,76 +1975,6 @@ public class GenericClientDstu2Test {
}
@Test
public void testSearchByNumber() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.greaterThan().number(123))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=gt123", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.lessThan().number(123))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=lt123", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.greaterThanOrEqual().number("123"))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=ge123", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.lessThanOrEqual().number("123"))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=le123", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Encounter")
.where(Encounter.LENGTH.exactly().number(123))
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Encounter?length=123", capt.getAllValues().get(idx).getURI().toString());
idx++;
}
@Test
public void testSearchByUrl() throws Exception {
@ -2630,9 +2643,61 @@ public class GenericClientDstu2Test {
return (OperationOutcome) theOperationOutcome;
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() {
ourCtx = FhirContext.forDstu2();
}
public final static class RestfulClientInstance implements IRestfulClient {
@Override
public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) {
return null;
}
@Override
public FhirContext getFhirContext() {
return null;
}
@Override
public IHttpClient getHttpClient() {
return null;
}
@Override
public String getServerBase() {
return null;
}
@Override
public void registerInterceptor(IClientInterceptor theInterceptor) {
//nothing
}
@Override
public void setEncoding(EncodingEnum theEncoding) {
//nothing
}
@Override
public void setPrettyPrint(Boolean thePrettyPrint) {
//nothing
}
@Override
public void setSummary(SummaryEnum theSummary) {
//nothing
}
@Override
public void unregisterInterceptor(IClientInterceptor theInterceptor) {
//nothing
}
}
}

View File

@ -0,0 +1,40 @@
package ca.uhn.fhir.rest.client.apache;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.BaseClient;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
public class ApacheRestfulClientFactoryTest {
@Test
public void testSetContext() {
ApacheRestfulClientFactory factory = new ApacheRestfulClientFactory();
factory.getServerValidationModeEnum();
factory.setFhirContext(FhirContext.forDstu2());
try {
factory.setFhirContext(FhirContext.forDstu2());
fail();
} catch (IllegalStateException e) {
assertEquals("java.lang.IllegalStateException: RestfulClientFactory instance is already associated with one FhirContext. RestfulClientFactory instances can not be shared.", e.toString());
}
}
@Test
public void testValidatateBase() {
FhirContext ctx = FhirContext.forDstu2();
ApacheRestfulClientFactory factory = new ApacheRestfulClientFactory();
factory.setFhirContext(ctx);
factory.setConnectTimeout(1);
try {
factory.validateServerBase("http://127.0.0.1:22225", factory.getHttpClient("http://foo"), (BaseClient) ctx.newRestfulGenericClient("http://foo"));
fail();
} catch (FhirClientConnectionException e) {
assertEquals("Failed to retrieve the server metadata statement during client initialization. URL used was http://127.0.0.1:22225metadata", e.getMessage());
}
}
}

View File

@ -40,6 +40,7 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
@ -47,6 +48,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
@ -158,6 +160,215 @@ public class AuthorizationInterceptorDstu2Test {
assertTrue(ourHitMethod);
}
@Test
public void testOperationServerLevel() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onServer().andThen()
.build();
//@formatter:on
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Server
ourHitMethod = false;
ourReturn = Arrays.asList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Type
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Instance
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testOperationInstanceLevel() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onInstance(new IdDt("http://example.com/Patient/1/_history/2")).andThen()
.build();
//@formatter:on
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Server
ourHitMethod = false;
ourReturn = Arrays.asList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Type
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Instance
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Wrong instance
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testOperationAnyName() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("RULE 1").operation().withAnyName().onServer().andThen()
.build();
//@formatter:on
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Server
ourHitMethod = false;
ourReturn = Arrays.asList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@Test
public void testOperationTypeLevel() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Patient.class).andThen()
.build();
//@formatter:on
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Server
ourHitMethod = false;
ourReturn = Arrays.asList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Type
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Wrong type
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/1/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Wrong name
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName2");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Instance
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testDenyAll() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -762,6 +973,17 @@ public class AuthorizationInterceptorDstu2Test {
retVal.setResource(theResource);
return retVal;
}
@Operation(name="opName", idempotent=true)
public Parameters operation() {
ourHitMethod = true;
return (Parameters) new Parameters().setId("1");
}
@Operation(name="opName", idempotent=true)
public Parameters operation(@IdParam IdDt theId) {
ourHitMethod = true;
return (Parameters) new Parameters().setId("1");
}
}
@ -813,11 +1035,37 @@ public class AuthorizationInterceptorDstu2Test {
retVal.setResource(theResource);
return retVal;
}
@Operation(name="opName", idempotent=true)
public Parameters operation() {
ourHitMethod = true;
return (Parameters) new Parameters().setId("1");
}
@Operation(name="opName", idempotent=true)
public Parameters operation(@IdParam IdDt theId) {
ourHitMethod = true;
return (Parameters) new Parameters().setId("1");
}
@Operation(name="opName2", idempotent=true)
public Parameters operation2(@IdParam IdDt theId) {
ourHitMethod = true;
return (Parameters) new Parameters().setId("1");
}
}
public static class PlainProvider
{
@Operation(name="opName", idempotent=true)
public Parameters operation() {
ourHitMethod = true;
return (Parameters) new Parameters().setId("1");
}
@Transaction()
public Bundle search(@TransactionParam Bundle theInput) {
ourHitMethod = true;

View File

@ -377,6 +377,10 @@
response message
</action>
-->
<action type="add">
AuthorizationInterceptor can now allow or deny requests to extended
operations (e.g. $everything)
</action>
</release>
<release version="1.5" date="2016-04-20">
<action type="fix" issue="339">