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

This commit is contained in:
James 2016-08-22 07:42:02 -04:00
commit 35a890db34
45 changed files with 12892 additions and 949 deletions

View File

@ -0,0 +1,84 @@
package example.customtype;
import org.hl7.fhir.dstu3.model.BackboneElement;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.StringType;
import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.util.ElementUtil;
//START SNIPPET: resource
@ResourceDef(name = "Patient")
public class CustomCompositeExtension extends Patient {
private static final long serialVersionUID = 1L;
/**
* A custom extension
*/
@Child(name = "foo")
@Extension(url="http://acme.org/fooParent", definedLocally = false, isModifier = false)
protected FooParentExtension fooParentExtension;
public FooParentExtension getFooParentExtension() {
return fooParentExtension;
}
@Override
public boolean isEmpty() {
return super.isEmpty() && ElementUtil.isEmpty(fooParentExtension);
}
public void setFooParentExtension(FooParentExtension theFooParentExtension) {
fooParentExtension = theFooParentExtension;
}
@Block
public static class FooParentExtension extends BackboneElement {
private static final long serialVersionUID = 4522090347756045145L;
@Child(name = "childA")
@Extension(url = "http://acme.org/fooChildA", definedLocally = false, isModifier = false)
private StringType myChildA;
@Child(name = "childB")
@Extension(url = "http://acme.org/fooChildB", definedLocally = false, isModifier = false)
private StringType myChildB;
@Override
public FooParentExtension copy() {
FooParentExtension copy = new FooParentExtension();
copy.myChildA = myChildA;
copy.myChildB = myChildB;
return copy;
}
@Override
public boolean isEmpty() {
return super.isEmpty() && ElementUtil.isEmpty(myChildA, myChildB);
}
public StringType getChildA() {
return myChildA;
}
public StringType getChildB() {
return myChildB;
}
public void setChildA(StringType theChildA) {
myChildA = theChildA;
}
public void setChildB(StringType theChildB) {
myChildB = theChildB;
}
}
}
//END SNIPPET: resource

View File

@ -88,6 +88,10 @@ public enum FhirVersionEnum {
return ordinal() > theVersion.ordinal();
}
public boolean isOlderThan(FhirVersionEnum theVersion) {
return ordinal() < theVersion.ordinal();
}
/**
* Returns true if the given version is present on the classpath
*/

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.i18n;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR - Core Library
@ -10,7 +12,7 @@ package ca.uhn.fhir.i18n;
* 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
* 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,
@ -21,6 +23,8 @@ package ca.uhn.fhir.i18n;
*/
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
@ -30,40 +34,56 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class HapiLocalizer {
private ResourceBundle myBundle;
private List<ResourceBundle> myBundle = new ArrayList<ResourceBundle>();
private final Map<String, MessageFormat> myKeyToMessageFormat = new ConcurrentHashMap<String, MessageFormat>();
public HapiLocalizer() {
myBundle = ResourceBundle.getBundle(HapiLocalizer.class.getPackage().getName() + ".hapi-messages");
this(HapiLocalizer.class.getPackage().getName() + ".hapi-messages");
}
public HapiLocalizer(String... theBundleNames) {
for (String nextName : theBundleNames) {
myBundle.add(ResourceBundle.getBundle(nextName));
}
}
public String getMessage(Class<?> theType, String theKey, Object... theParameters) {
return getMessage(theType.getName() + '.' + theKey, theParameters);
}
public String getMessage(String theQualifiedKey, Object... theParameters) {
if (theParameters != null && theParameters.length > 0) {
MessageFormat format = myKeyToMessageFormat.get(theQualifiedKey);
if (format != null) {
return format.format(theParameters).toString();
}
String formatString = myBundle.getString(theQualifiedKey);
if (formatString== null) {
formatString = "!MESSAGE!";
}
String formatString = findFormatString(theQualifiedKey);
format = new MessageFormat(formatString.trim());
myKeyToMessageFormat.put(theQualifiedKey, format);
return format.format(theParameters).toString();
} else {
String retVal = myBundle.getString(theQualifiedKey);
if (retVal == null) {
retVal = "!MESSAGE!";
}
String retVal = findFormatString(theQualifiedKey);
return retVal;
}
}
private String findFormatString(String theQualifiedKey) {
String formatString = null;
for (ResourceBundle nextBundle : myBundle) {
if (nextBundle.containsKey(theQualifiedKey)) {
formatString = nextBundle.getString(theQualifiedKey);
}
if (isNotBlank(formatString)) {
break;
}
}
if (formatString == null) {
formatString = "!MESSAGE!";
}
return formatString;
}
}

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.parser;
* 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
* 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,
@ -285,13 +285,17 @@ public abstract class BaseParser implements IParser {
IIdType refId = theRef.getResource().getIdElement();
if (refId != null) {
if (refId.hasIdPart()) {
if (!refId.hasResourceType()) {
refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
}
if (isStripVersionsFromReferences(theCompositeChildElement)) {
reference = refId.toVersionless().getValue();
} else {
if (refId.getValue().startsWith("urn:")) {
reference = refId.getValue();
} else {
if (!refId.hasResourceType()) {
refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
}
if (isStripVersionsFromReferences(theCompositeChildElement)) {
reference = refId.toVersionless().getValue();
} else {
reference = refId.getValue();
}
}
}
}
@ -388,7 +392,8 @@ public abstract class BaseParser implements IParser {
Validate.notNull(theWriter, "theWriter can not be null");
if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
throw new IllegalArgumentException("This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
throw new IllegalArgumentException(
"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
}
doEncodeResourceToWriter(theResource, theWriter);
@ -430,9 +435,9 @@ public abstract class BaseParser implements IParser {
String childName = theChild.getChildNameByDatatype(type);
BaseRuntimeElementDefinition<?> childDef = theChild.getChildElementDefinitionByDatatype(type);
if (childDef == null) {
// if (theValue instanceof IBaseExtension) {
// return null;
// }
// if (theValue instanceof IBaseExtension) {
// return null;
// }
/*
* For RI structures Enumeration class, this replaces the child def
@ -578,7 +583,8 @@ public abstract class BaseParser implements IParser {
}
protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false && theIncludedResource == false;
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false
&& theIncludedResource == false;
}
@Override
@ -623,7 +629,7 @@ public abstract class BaseParser implements IParser {
@Override
public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException {
/*
/*
* We do this so that the context can verify that the structure is for
* the correct FHIR version
*/
@ -698,7 +704,8 @@ public abstract class BaseParser implements IParser {
return parseTagList(new StringReader(theString));
}
protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues, CompositeChildElement theCompositeChildElement) {
protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
CompositeChildElement theCompositeChildElement) {
if (myContext.getVersion().getVersion().isRi()) {
/*
@ -1156,11 +1163,11 @@ public abstract class BaseParser implements IParser {
retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), true);
}
}
// if (retVal == false && myEncodeElements.contains("*.(mandatory)")) {
// if (myDef.getMin() > 0) {
// retVal = true;
// }
// }
// if (retVal == false && myEncodeElements.contains("*.(mandatory)")) {
// if (myDef.getMin() > 0) {
// retVal = true;
// }
// }
return retVal;
}

View File

@ -104,6 +104,10 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
if (getContext().getVersion().getVersion() == FhirVersionEnum.DSTU1) {
resource.setId(urlId);
} else {
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) == false) {
resource.setId(theRequest.getId());
}
String matchUrl = null;
if (myConditionalUrlIndex != -1) {
matchUrl = (String) theParams[myConditionalUrlIndex];

View File

@ -67,29 +67,7 @@ class ConditionalParamBinder implements IParameter {
@Override
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
if (myOperationType == RestOperationTypeEnum.CREATE) {
String retVal = theRequest.getHeader(Constants.HEADER_IF_NONE_EXIST);
if (isBlank(retVal)) {
return null;
}
if (retVal.startsWith(theRequest.getFhirServerBase())) {
retVal = retVal.substring(theRequest.getFhirServerBase().length());
}
return retVal;
} else if (myOperationType != RestOperationTypeEnum.DELETE && myOperationType != RestOperationTypeEnum.UPDATE) {
return null;
}
if (theRequest.getId() != null && theRequest.getId().hasIdPart()) {
return null;
}
int questionMarkIndex = theRequest.getCompleteUrl().indexOf('?');
if (questionMarkIndex == -1) {
return null;
}
return theRequest.getResourceName() + theRequest.getCompleteUrl().substring(questionMarkIndex);
return theRequest.getConditionalUrl(myOperationType);
}
}

View File

@ -27,8 +27,10 @@ import java.util.Collections;
import java.util.Set;
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.FhirVersionEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
@ -79,9 +81,13 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInUrlForCreate", theUrlId);
throw new InvalidRequestException(msg);
}
if (isNotBlank(theResourceId)) {
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInBodyForCreate", theResourceId);
throw new InvalidRequestException(msg);
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
if (isNotBlank(theResourceId)) {
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInBodyForCreate", theResourceId);
throw new InvalidRequestException(msg);
}
} else {
theResource.setId((IIdType)null);
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.method;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
@ -38,6 +40,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IRestfulResponse;
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@ -52,6 +55,7 @@ public abstract class RequestDetails {
private String myOperation;
private Map<String, String[]> myParameters;
private byte[] myRequestContents;
private IRequestOperationCallback myRequestOperationCallback = new RequestOperationCallback();
private String myRequestPath;
private RequestTypeEnum myRequestType;
private String myResourceName;
@ -59,7 +63,6 @@ public abstract class RequestDetails {
private IRestfulResponse myResponse;
private RestOperationTypeEnum myRestOperationType;
private String mySecondaryOperation;
private IRequestOperationCallback myRequestOperationCallback = new RequestOperationCallback();
private Map<String, List<String>> myUnqualifiedToQualifiedNames;
private Map<Object, Object> myUserData;
protected abstract byte[] getByteStreamRequestContents();
@ -76,6 +79,40 @@ public abstract class RequestDetails {
return myCompleteUrl;
}
/**
* Returns the <b>conditional URL</b> if this request has one, or <code>null</code> otherwise. For an
* update or delete method, this is the part of the URL after the <code>?</code>. For a create, this
* is the value of the <code>If-None-Exist</code> header.
*
* @param theOperationType The operation type to find the conditional URL for
* @return Returns the <b>conditional URL</b> if this request has one, or <code>null</code> otherwise
*/
public String getConditionalUrl(RestOperationTypeEnum theOperationType) {
if (theOperationType == RestOperationTypeEnum.CREATE) {
String retVal = this.getHeader(Constants.HEADER_IF_NONE_EXIST);
if (isBlank(retVal)) {
return null;
}
if (retVal.startsWith(this.getFhirServerBase())) {
retVal = retVal.substring(this.getFhirServerBase().length());
}
return retVal;
} else if (theOperationType != RestOperationTypeEnum.DELETE && theOperationType != RestOperationTypeEnum.UPDATE) {
return null;
}
if (this.getId() != null && this.getId().hasIdPart()) {
return null;
}
int questionMarkIndex = this.getCompleteUrl().indexOf('?');
if (questionMarkIndex == -1) {
return null;
}
return this.getResourceName() + this.getCompleteUrl().substring(questionMarkIndex);
}
/**
* The fhir server base url, independant of the query being executed
*
@ -138,6 +175,15 @@ public abstract class RequestDetails {
*/
public abstract Reader getReader() throws IOException;
/**
* Returns an invoker that can be called from user code to advise the server interceptors
* of any nested operations being invoked within operations. This invoker acts as a proxy for
* all interceptors
*/
public IRequestOperationCallback getRequestOperationCallback() {
return myRequestOperationCallback;
}
/**
* The part of the request URL that comes after the server base.
* <p>
@ -175,15 +221,6 @@ public abstract class RequestDetails {
*/
public abstract String getServerBaseForRequest();
/**
* Returns an invoker that can be called from user code to advise the server interceptors
* of any nested operations being invoked within operations. This invoker acts as a proxy for
* all interceptors
*/
public IRequestOperationCallback getRequestOperationCallback() {
return myRequestOperationCallback;
}
public Map<String, List<String>> getUnqualifiedToQualifiedNames() {
return myUnqualifiedToQualifiedNames;
}
@ -290,13 +327,20 @@ public abstract class RequestDetails {
public void setRestOperationType(RestOperationTypeEnum theRestOperationType) {
myRestOperationType = theRestOperationType;
}
public void setSecondaryOperation(String theSecondaryOperation) {
mySecondaryOperation = theSecondaryOperation;
}
private class RequestOperationCallback implements IRequestOperationCallback {
private List<IServerInterceptor> getInterceptors() {
if (getServer() == null) {
return Collections.emptyList();
}
return getServer().getInterceptors();
}
@Override
public void resourceCreated(IBaseResource theResource) {
for (IServerInterceptor next : getInterceptors()) {
@ -306,13 +350,6 @@ public abstract class RequestDetails {
}
}
private List<IServerInterceptor> getInterceptors() {
if (getServer() == null) {
return Collections.emptyList();
}
return getServer().getInterceptors();
}
@Override
public void resourceDeleted(IBaseResource theResource) {
for (IServerInterceptor next : getInterceptors()) {
@ -322,15 +359,6 @@ public abstract class RequestDetails {
}
}
@Override
public void resourceUpdated(IBaseResource theResource) {
for (IServerInterceptor next : getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourceUpdated(RequestDetails.this, theResource);
}
}
}
@Override
public void resourcesCreated(Collection<? extends IBaseResource> theResource) {
for (IBaseResource next : theResource) {
@ -352,6 +380,15 @@ public abstract class RequestDetails {
}
}
@Override
public void resourceUpdated(IBaseResource theResource) {
for (IServerInterceptor next : getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourceUpdated(RequestDetails.this, theResource);
}
}
}
}
}

View File

@ -11,7 +11,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* 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
* 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,
@ -30,6 +30,7 @@ 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.FhirVersionEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Update;
@ -62,7 +63,8 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
id.setValue(locationHeader);
if (isNotBlank(id.getResourceType())) {
if (!getResourceName().equals(id.getResourceType())) {
throw new InvalidRequestException("Attempting to update '" + getResourceName() + "' but content-location header specifies different resource type '" + id.getResourceType() + "' - header value: " + locationHeader);
throw new InvalidRequestException(
"Attempting to update '" + getResourceName() + "' but content-location header specifies different resource type '" + id.getResourceType() + "' - header value: " + locationHeader);
}
}
}
@ -140,17 +142,20 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "noIdInUrlForUpdate");
throw new InvalidRequestException(msg);
}
if (isBlank(theResourceId)) {
// String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "noIdInBodyForUpdate");
ourLog.warn("No resource ID found in resource body for update");
theResource.setId(theUrlId);
} else {
if (!theResourceId.equals(theUrlId)) {
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "incorrectIdForUpdate", theResourceId, theUrlId);
throw new InvalidRequestException(msg);
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
if (isBlank(theResourceId)) {
ourLog.warn("No resource ID found in resource body for update");
theResource.setId(theUrlId);
} else {
if (!theResourceId.equals(theUrlId)) {
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "incorrectIdForUpdate", theResourceId, theUrlId);
throw new InvalidRequestException(msg);
}
}
}
} else {
theResource.setId((IIdType)null);
}
}
}

View File

@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
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;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
@ -78,8 +79,8 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
setDefaultPolicy(theDefaultPolicy);
}
private void applyRulesAndFailIfDeny(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
Verdict decision = applyRulesAndReturnDecision(theOperation, theRequestDetails, theInputResource, theOutputResource);
private void applyRulesAndFailIfDeny(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource) {
Verdict decision = applyRulesAndReturnDecision(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
if (decision.getDecision() == PolicyEnum.ALLOW) {
return;
@ -89,13 +90,13 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
}
@Override
public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource) {
List<IAuthRule> rules = buildRuleList(theRequestDetails);
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, theOutputResource, this);
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, this);
if (verdict != null) {
ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision());
break;
@ -126,7 +127,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
}
private OperationExamineDirection determineOperationDirection(RestOperationTypeEnum theOperation) {
private OperationExamineDirection determineOperationDirection(RestOperationTypeEnum theOperation, IBaseResource theRequestResource) {
switch (theOperation) {
case ADD_TAGS:
case DELETE_TAGS:
@ -147,6 +148,13 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
case CREATE:
case UPDATE:
// if (theRequestResource != null) {
// if (theRequestResource.getIdElement() != null) {
// if (theRequestResource.getIdElement().hasIdPart() == false) {
// return OperationExamineDirection.IN_UNCATEGORIZED;
// }
// }
// }
return OperationExamineDirection.IN;
case META:
@ -204,14 +212,26 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
}
private void handleUserOperation(RequestDetails theRequest, IBaseResource theResource, RestOperationTypeEnum operation) {
applyRulesAndFailIfDeny(operation, theRequest, theResource, null);
applyRulesAndFailIfDeny(operation, theRequest, theResource, theResource.getIdElement(), null);
}
@Override
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest) {
switch (determineOperationDirection(theOperation)) {
IBaseResource inputResource = null;
IIdType inputResourceId = null;
switch (determineOperationDirection(theOperation, theProcessedRequest.getResource())) {
case IN_UNCATEGORIZED:
inputResourceId = theProcessedRequest.getId();
if (inputResourceId == null || inputResourceId.hasIdPart() == false) {
return;
} else {
break;
}
case IN:
case BOTH:
inputResource = theProcessedRequest.getResource();
inputResourceId = theProcessedRequest.getId();
break;
case NONE:
case OUT:
@ -219,7 +239,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
}
RequestDetails requestDetails = theProcessedRequest.getRequestDetails();
applyRulesAndFailIfDeny(theOperation, requestDetails, theProcessedRequest.getResource(), null);
applyRulesAndFailIfDeny(theOperation, requestDetails, inputResource, inputResourceId, null);
}
@Override
@ -236,7 +256,9 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
switch (determineOperationDirection(theRequestDetails.getRestOperationType())) {
switch (determineOperationDirection(theRequestDetails.getRestOperationType(), null)) {
case IN_UNCATEGORIZED:
return true;
case IN:
case NONE:
return true;
@ -246,8 +268,6 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
}
FhirContext fhirContext = theRequestDetails.getServer().getFhirContext();
List<IAuthRule> rules = buildRuleList(theRequestDetails);
List<IBaseResource> resources = Collections.emptyList();
switch (theRequestDetails.getRestOperationType()) {
@ -272,7 +292,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
}
for (IBaseResource nextResponse : resources) {
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, null, nextResponse);
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, null, null, nextResponse);
}
return true;
@ -334,9 +354,10 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
private enum OperationExamineDirection {
IN,
IN_UNCATEGORIZED,
NONE,
OUT,
BOTH
BOTH,
}
public static class Verdict {

View File

@ -35,7 +35,7 @@ abstract class BaseRule implements IAuthRule {
return myName;
}
public void setMode(PolicyEnum theRuleMode) {
void setMode(PolicyEnum theRuleMode) {
myMode = theRuleMode;
}
@ -43,7 +43,7 @@ abstract class BaseRule implements IAuthRule {
return new Verdict(myMode, this);
}
public PolicyEnum getMode() {
PolicyEnum getMode() {
return myMode;
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
*/
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
@ -37,6 +38,7 @@ public interface IAuthRule {
* The request
* @param theInputResource
* The resource being input by the client, or <code>null</code>
* @param theInputResourceId TODO
* @param theOutputResource
* The resource being returned by the server, or <code>null</code>
* @param theRuleApplier
@ -44,7 +46,7 @@ public interface IAuthRule {
* nested objects in the request, such as nested requests in a transaction)
* @return Returns a policy decision, or <code>null</code> if the rule does not apply
*/
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier);
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier);
/**
* Returns a name for this rule, to be used in logs and error messages

View File

@ -0,0 +1,17 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import org.hl7.fhir.instance.model.api.IBaseResource;
public interface IAuthRuleBuilderAppliesTo<T> {
/**
* Rule applies to resources of the given type
*/
T resourcesOfType(Class<? extends IBaseResource> theType);
/**
* Rule applies to all resources
*/
T allResources();
}

View File

@ -22,11 +22,39 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
public interface IAuthRuleBuilderRule {
/**
* This rule applies to <code>create</code> operations with a <code>conditional</code>
* URL as a part of the request. Note that this rule will allow the conditional
* operation to proceed, but the server is expected to determine the actual target
* of the conditional request and send a subsequent event to the {@link AuthorizationInterceptor}
* in order to authorize the actual target.
* <p>
* In other words, if the server is configured correctly, this chain will allow the
* client to perform a conditional update, but a different rule is required to actually
* authorize the target that the conditional update is determined to match.
* </p>
*/
IAuthRuleBuilderRuleConditional createConditional();
/**
* This rule applies to the FHIR delete operation
*/
IAuthRuleBuilderRuleOp delete();
/**
* This rule applies to <code>create</code> operations with a <code>conditional</code>
* URL as a part of the request. Note that this rule will allow the conditional
* operation to proceed, but the server is expected to determine the actual target
* of the conditional request and send a subsequent event to the {@link AuthorizationInterceptor}
* in order to authorize the actual target.
* <p>
* In other words, if the server is configured correctly, this chain will allow the
* client to perform a conditional update, but a different rule is required to actually
* authorize the target that the conditional update is determined to match.
* </p>
*/
IAuthRuleBuilderRuleConditional deleteConditional();
/**
* This rules applies to the metadata operation (retrieve the
* server's conformance statement)
@ -36,6 +64,11 @@ public interface IAuthRuleBuilderRule {
*/
IAuthRuleBuilderRuleOpClassifierFinished metadata();
/**
* This rule applies to a FHIR operation (e.g. <code>$validate</code>)
*/
IAuthRuleBuilderOperation operation();
/**
* This rule applies to any FHIR operation involving reading, including
* <code>read</code>, <code>vread</code>, <code>search</code>, and
@ -49,15 +82,24 @@ public interface IAuthRuleBuilderRule {
*/
IAuthRuleBuilderRuleTransaction transaction();
/**
* This rule applies to <code>update</code> operations with a <code>conditional</code>
* URL as a part of the request. Note that this rule will allow the conditional
* operation to proceed, but the server is expected to determine the actual target
* of the conditional request and send a subsequent event to the {@link AuthorizationInterceptor}
* in order to authorize the actual target.
* <p>
* In other words, if the server is configured correctly, this chain will allow the
* client to perform a conditional update, but a different rule is required to actually
* authorize the target that the conditional update is determined to match.
* </p>
*/
IAuthRuleBuilderRuleConditional updateConditional();
/**
* This rule applies to any FHIR operation involving writing, including
* <code>create</code>, and <code>update</code>
*/
IAuthRuleBuilderRuleOp write();
/**
* This rule applies to a FHIR operation (e.g. <code>$validate</code>)
*/
IAuthRuleBuilderOperation operation();
}

View File

@ -0,0 +1,5 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
public interface IAuthRuleBuilderRuleConditional extends IAuthRuleBuilderAppliesTo<IAuthRuleBuilderRuleConditionalClassifier> {
}

View File

@ -0,0 +1,27 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import java.util.List;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 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%
*/
public interface IAuthRuleBuilderRuleConditionalClassifier extends IAuthRuleFinished {
// nothing
}

View File

@ -1,37 +1,5 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 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 org.hl7.fhir.instance.model.api.IBaseResource;
public interface IAuthRuleBuilderRuleOp {
/**
* Rule applies to resources of the given type
*/
IAuthRuleBuilderRuleOpClassifier resourcesOfType(Class<? extends IBaseResource> theType);
/**
* Rule applies to all resources
*/
IAuthRuleBuilderRuleOpClassifier allResources();
public interface IAuthRuleBuilderRuleOp extends IAuthRuleBuilderAppliesTo<IAuthRuleBuilderRuleOpClassifier> {
// nothing
}

View File

@ -1,7 +1,5 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import java.util.List;
/*
* #%L
* HAPI FHIR - Core Library
@ -22,15 +20,6 @@ import java.util.List;
* #L%
*/
public interface IAuthRuleBuilderRuleOpClassifierFinished {
/**
* Start another rule
*/
IAuthRuleBuilder andThen();
/**
* Build the rule list
*/
List<IAuthRule> build();
public interface IAuthRuleBuilderRuleOpClassifierFinished extends IAuthRuleFinished {
// nothing
}

View File

@ -0,0 +1,17 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import java.util.List;
public interface IAuthRuleFinished {
/**
* Start another rule
*/
IAuthRuleBuilder andThen();
/**
* Build the rule list
*/
List<IAuthRule> build();
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
*/
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
@ -28,6 +29,6 @@ import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict
public interface IRuleApplier {
Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource);
Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource);
}

View File

@ -54,7 +54,7 @@ class OperationRule extends BaseRule implements IAuthRule {
}
@Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
boolean applies = false;
@ -67,7 +67,7 @@ class OperationRule extends BaseRule implements IAuthRule {
case EXTENDED_OPERATION_TYPE:
if (myAppliesToTypes != null) {
for (Class<? extends IBaseResource> next : myAppliesToTypes) {
String resName = ctx.getResourceDefinition(theRequestDetails.getResourceName()).getName();
String resName = ctx.getResourceDefinition(next).getName();
if (resName.equals(theRequestDetails.getResourceName())) {
applies = true;
break;

View File

@ -1,7 +1,5 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import java.util.ArrayList;
/*
* #%L
* HAPI FHIR - Core Library
@ -12,7 +10,7 @@ import java.util.ArrayList;
* 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
* 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,
@ -21,17 +19,14 @@ import java.util.ArrayList;
* limitations under the License.
* #L%
*/
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
public class RuleBuilder implements IAuthRuleBuilder {
private ArrayList<IAuthRule> myRules;
@ -39,7 +34,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
public RuleBuilder() {
myRules = new ArrayList<IAuthRule>();
}
@Override
public IAuthRuleBuilderRule allow() {
return allow(null);
@ -57,7 +52,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
@Override
public IAuthRuleBuilderRuleOpClassifierFinished allowAll(String theRuleName) {
myRules.add(new Rule(theRuleName).setOp(RuleOpEnum.ALLOW_ALL));
myRules.add(new RuleImplOp(theRuleName).setOp(RuleOpEnum.ALLOW_ALL));
return new RuleBuilderFinished();
}
@ -83,21 +78,30 @@ public class RuleBuilder implements IAuthRuleBuilder {
@Override
public IAuthRuleBuilderRuleOpClassifierFinished denyAll(String theRuleName) {
myRules.add(new Rule(theRuleName).setOp(RuleOpEnum.DENY_ALL));
myRules.add(new RuleImplOp(theRuleName).setOp(RuleOpEnum.DENY_ALL));
return new RuleBuilderFinished();
}
private final class RuleBuilderFinished implements IAuthRuleBuilderRuleOpClassifierFinished {
private class RuleBuilderFinished implements IAuthRuleFinished, IAuthRuleBuilderRuleOpClassifierFinished {
@Override
public IAuthRuleBuilder andThen() {
doBuildRule();
return RuleBuilder.this;
}
@Override
public List<IAuthRule> build() {
doBuildRule();
return myRules;
}
/**
* Subclasses may override
*/
protected void doBuildRule() {
// nothing
}
}
private class RuleBuilderRule implements IAuthRuleBuilderRule {
@ -111,39 +115,100 @@ public class RuleBuilder implements IAuthRuleBuilder {
myRuleName = theRuleName;
}
@Override
public IAuthRuleBuilderRuleConditional createConditional() {
return new RuleBuilderRuleConditional(RestOperationTypeEnum.CREATE);
}
@Override
public IAuthRuleBuilderRuleOp delete() {
myRuleOp = RuleOpEnum.DELETE;
return new RuleBuilderRuleOp();
}
@Override
public IAuthRuleBuilderRuleConditional deleteConditional() {
return new RuleBuilderRuleConditional(RestOperationTypeEnum.DELETE);
}
@Override
public RuleBuilderFinished metadata() {
Rule rule = new Rule(myRuleName);
RuleImplOp rule = new RuleImplOp(myRuleName);
rule.setOp(RuleOpEnum.METADATA);
rule.setMode(myRuleMode);
myRules.add(rule);
return new RuleBuilderFinished();
}
@Override
public IAuthRuleBuilderOperation operation() {
return new RuleBuilderRuleOperation();
}
@Override
public IAuthRuleBuilderRuleOp read() {
myRuleOp = RuleOpEnum.READ;
return new RuleBuilderRuleOp();
}
@Override
public IAuthRuleBuilderRuleTransaction transaction() {
myRuleOp = RuleOpEnum.TRANSACTION;
return new RuleBuilderRuleTransaction();
}
@Override
public IAuthRuleBuilderRuleConditional updateConditional() {
return new RuleBuilderRuleConditional(RestOperationTypeEnum.UPDATE);
}
@Override
public IAuthRuleBuilderRuleOp write() {
myRuleOp = RuleOpEnum.WRITE;
return new RuleBuilderRuleOp();
}
private class RuleBuilderRuleConditional implements IAuthRuleBuilderRuleConditional {
private AppliesTypeEnum myAppliesTo;
private Set<?> myAppliesToTypes;
private RestOperationTypeEnum myOperationType;
public RuleBuilderRuleConditional(RestOperationTypeEnum theOperationType) {
myOperationType = theOperationType;
}
@Override
public IAuthRuleBuilderRuleConditionalClassifier allResources() {
myAppliesTo = AppliesTypeEnum.ALL_RESOURCES;
return new RuleBuilderRuleConditionalClassifier();
}
@Override
public IAuthRuleBuilderRuleConditionalClassifier resourcesOfType(Class<? extends IBaseResource> theType) {
Validate.notNull(theType, "theType must not be null");
myAppliesTo = AppliesTypeEnum.TYPES;
myAppliesToTypes = Collections.singleton(theType);
return new RuleBuilderRuleConditionalClassifier();
}
public class RuleBuilderRuleConditionalClassifier extends RuleBuilderFinished implements IAuthRuleBuilderRuleConditionalClassifier {
@Override
protected void doBuildRule() {
RuleImplConditional rule = new RuleImplConditional(myRuleName);
rule.setMode(myRuleMode);
rule.setOperationType(myOperationType);
rule.setAppliesTo(myAppliesTo);
rule.setAppliesToTypes(myAppliesToTypes);
myRules.add(rule);
}
}
}
private class RuleBuilderRuleOp implements IAuthRuleBuilderRuleOp {
private AppliesTypeEnum myAppliesTo;
@ -170,8 +235,8 @@ public class RuleBuilder implements IAuthRuleBuilder {
private Collection<? extends IIdType> myInCompartmentOwners;
private IAuthRuleBuilderRuleOpClassifierFinished finished() {
Rule rule = new Rule(myRuleName);
RuleImplOp rule = new RuleImplOp(myRuleName);
rule.setMode(myRuleMode);
rule.setOp(myRuleOp);
rule.setAppliesTo(myAppliesTo);
@ -180,7 +245,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
rule.setClassifierCompartmentName(myInCompartmentName);
rule.setClassifierCompartmentOwners(myInCompartmentOwners);
myRules.add(rule);
return new RuleBuilderFinished();
}
@ -222,29 +287,18 @@ public class RuleBuilder implements IAuthRuleBuilder {
}
private class RuleBuilderRuleTransaction implements IAuthRuleBuilderRuleTransaction {
private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
@Override
public IAuthRuleBuilderRuleTransactionOp withAnyOperation() {
return new RuleBuilderRuleTransactionOp();
public IAuthRuleBuilderOperationNamed named(String theOperationName) {
Validate.notBlank(theOperationName, "theOperationName must not be null or empty");
return new RuleBuilderRuleOperationNamed(theOperationName);
}
private class RuleBuilderRuleTransactionOp implements IAuthRuleBuilderRuleTransactionOp {
@Override
public IAuthRuleBuilderRuleOpClassifierFinished andApplyNormalRules() {
Rule rule = new Rule(myRuleName);
rule.setMode(myRuleMode);
rule.setOp(myRuleOp);
rule.setTransactionAppliesToOp(TransactionAppliesToEnum.ANY_OPERATION);
myRules.add(rule);
return new RuleBuilderFinished();
}
@Override
public IAuthRuleBuilderOperationNamed withAnyName() {
return new RuleBuilderRuleOperationNamed(null);
}
}
private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
private class RuleBuilderRuleOperationNamed implements IAuthRuleBuilderOperationNamed {
@ -258,14 +312,6 @@ public class RuleBuilder implements IAuthRuleBuilder {
}
}
@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);
@ -273,24 +319,12 @@ public class RuleBuilder implements IAuthRuleBuilder {
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);
@ -298,26 +332,54 @@ public class RuleBuilder implements IAuthRuleBuilder {
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 IAuthRuleBuilderRuleOpClassifierFinished onServer() {
OperationRule rule = createRule();
rule.appliesToServer();
myRules.add(rule);
return new RuleBuilderFinished();
}
@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 IAuthRuleBuilderOperationNamed withAnyName() {
return new RuleBuilderRuleOperationNamed(null);
}
}
@Override
public IAuthRuleBuilderOperation operation() {
return new RuleBuilderRuleOperation();
private class RuleBuilderRuleTransaction implements IAuthRuleBuilderRuleTransaction {
@Override
public IAuthRuleBuilderRuleTransactionOp withAnyOperation() {
return new RuleBuilderRuleTransactionOp();
}
private class RuleBuilderRuleTransactionOp implements IAuthRuleBuilderRuleTransactionOp {
@Override
public IAuthRuleBuilderRuleOpClassifierFinished andApplyNormalRules() {
RuleImplOp rule = new RuleImplOp(myRuleName);
rule.setMode(myRuleMode);
rule.setOp(myRuleOp);
rule.setTransactionAppliesToOp(TransactionAppliesToEnum.ANY_OPERATION);
myRules.add(rule);
return new RuleBuilderFinished();
}
}
}
}
}

View File

@ -0,0 +1,63 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import java.util.Set;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
public class RuleImplConditional extends BaseRule implements IAuthRule {
private AppliesTypeEnum myAppliesTo;
private Set<?> myAppliesToTypes;
private RestOperationTypeEnum myOperationType;
public RuleImplConditional(String theRuleName) {
super(theRuleName);
}
@Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
IRuleApplier theRuleApplier) {
if (theInputResourceId != null) {
return null;
}
if (theOperation == myOperationType) {
switch (myAppliesTo) {
case ALL_RESOURCES:
break;
case TYPES:
if (theInputResource == null || !myAppliesToTypes.contains(theInputResource.getClass())) {
return null;
}
break;
}
if (theRequestDetails.getConditionalUrl(myOperationType) == null) {
return null;
}
return newVerdict();
}
return null;
}
void setAppliesTo(AppliesTypeEnum theAppliesTo) {
myAppliesTo = theAppliesTo;
}
void setAppliesToTypes(Set<?> theAppliesToTypes) {
myAppliesToTypes = theAppliesToTypes;
}
void setOperationType(RestOperationTypeEnum theOperationType) {
myOperationType = theOperationType;
}
}

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* 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
* 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,
@ -39,7 +39,7 @@ import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
import ca.uhn.fhir.util.FhirTerser;
class Rule extends BaseRule implements IAuthRule {
class RuleImplOp extends BaseRule implements IAuthRule {
private AppliesTypeEnum myAppliesTo;
private Set<?> myAppliesToTypes;
@ -49,34 +49,37 @@ class Rule extends BaseRule implements IAuthRule {
private RuleOpEnum myOp;
private TransactionAppliesToEnum myTransactionAppliesToOp;
public Rule(String theRuleName) {
public RuleImplOp(String theRuleName) {
super(theRuleName);
}
@Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
IRuleApplier theRuleApplier) {
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
IBaseResource appliesTo;
IBaseResource appliesToResource;
IIdType appliesToResourceId = null;
switch (myOp) {
case READ:
if (theOutputResource == null) {
return null;
}
appliesTo = theOutputResource;
appliesToResource = theOutputResource;
break;
case WRITE:
if (theInputResource == null) {
if (theInputResource == null && theInputResourceId == null) {
return null;
}
appliesTo = theInputResource;
appliesToResource = theInputResource;
appliesToResourceId = theInputResourceId;
break;
case DELETE:
if (theOperation == RestOperationTypeEnum.DELETE) {
if (theInputResource == null) {
return newVerdict();
} else {
appliesTo = theInputResource;
appliesToResource = theInputResource;
}
} else {
return null;
@ -115,7 +118,7 @@ class Rule extends BaseRule implements IAuthRule {
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null);
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null, null);
if (newVerdict == null) {
continue;
} else if (verdict == null) {
@ -133,7 +136,7 @@ class Rule extends BaseRule implements IAuthRule {
if (nextPart.getResource() == null) {
continue;
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, nextPart.getResource());
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextPart.getResource());
if (newVerdict == null) {
continue;
} else if (verdict == null) {
@ -165,8 +168,16 @@ class Rule extends BaseRule implements IAuthRule {
case ALL_RESOURCES:
break;
case TYPES:
if (myAppliesToTypes.contains(appliesTo.getClass()) == false) {
return null;
if (appliesToResource != null) {
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
return null;
}
}
if (appliesToResourceId != null) {
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
if (myAppliesToTypes.contains(type) == false) {
return null;
}
}
break;
default:
@ -180,9 +191,17 @@ class Rule extends BaseRule implements IAuthRule {
FhirTerser t = ctx.newTerser();
boolean foundMatch = false;
for (IIdType next : myClassifierCompartmentOwners) {
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesTo, next)) {
foundMatch = true;
break;
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 (!foundMatch) {
@ -196,7 +215,6 @@ class Rule extends BaseRule implements IAuthRule {
return newVerdict();
}
private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) {
if (!"Bundle".equals(theContext.getResourceDefinition(theInputResource).getName())) {
return false;
@ -238,8 +256,7 @@ class Rule extends BaseRule implements IAuthRule {
myClassifierType = theClassifierType;
}
public Rule setOp(RuleOpEnum theRuleOp) {
public RuleImplOp setOp(RuleOpEnum theRuleOp) {
myOp = theRuleOp;
return this;
}

View File

@ -386,9 +386,9 @@ public class AbstractJaxRsResourceProviderDstu3Test {
when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
compareResultId(1, patientCaptor.getValue());
assertEquals(null, patientCaptor.getValue().getIdElement().getIdPart());
assertEquals(null, patientCaptor.getValue().getIdElement().getVersionIdPart());
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
compareResultId(1, patientCaptor.getValue());
}
@SuppressWarnings("unchecked")

View File

@ -70,7 +70,6 @@ import ca.uhn.fhir.util.TestUtil;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class AbstractJaxRsResourceProviderTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AbstractJaxRsResourceProviderTest.class);
private static IGenericClient client;
@ -372,9 +371,8 @@ public class AbstractJaxRsResourceProviderTest {
when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
assertEquals("1", patientCaptor.getValue().getId().getIdPart());
assertEquals(null, patientCaptor.getValue().getId().getIdPart());
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
compareResultId(1, patientCaptor.getValue());
}
@SuppressWarnings("unchecked")

View File

@ -166,9 +166,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart());
throw new InvalidRequestException(message, createErrorOperationOutcome(message, "processing"));
}
} else {
} else if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedId", theResource.getIdElement().getIdPart());
throw new InvalidRequestException(message, createErrorOperationOutcome(message, "processing"));
} else {
// As of DSTU3, ID and version in the body should be ignored for a create/update
theResource.setId("");
}
}

View File

@ -58,7 +58,7 @@ public class DatabaseBackedPagingProvider extends FifoMemoryPagingProvider {
if (!provider.ensureSearchEntityLoaded()) {
return null;
}
return provider;
retVal = provider;
}
return retVal;
}

View File

@ -14,46 +14,16 @@ import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.model.Appointment;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.Device;
import org.hl7.fhir.dstu3.model.DiagnosticRequest;
import org.hl7.fhir.dstu3.model.DiagnosticReport;
import org.hl7.fhir.dstu3.model.Encounter;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Immunization;
import org.hl7.fhir.dstu3.model.Location;
import org.hl7.fhir.dstu3.model.Medication;
import org.hl7.fhir.dstu3.model.MedicationOrder;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Organization;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Period;
import org.hl7.fhir.dstu3.model.Practitioner;
import org.hl7.fhir.dstu3.model.Quantity;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.dstu3.model.Substance;
import org.hl7.fhir.dstu3.model.TemporalPrecisionEnum;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -63,35 +33,13 @@ import org.junit.Test;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.entity.ResourceLink;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;

View File

@ -634,17 +634,13 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
}
@Test
public void testCreateTextIdFails() {
public void testCreateTextIdDoesntFail() {
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue("testCreateTextIdFails");
p.addName().addFamily("Hello");
p.setId("Patient/ABC");
try {
myPatientDao.create(p, mySrd);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Can not create resource with ID[ABC], ID must not be supplied"));
}
String id = myPatientDao.create(p, mySrd).getId().getIdPart();
assertNotEquals("ABC", id);
}
@Test
@ -678,20 +674,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), empty());
}
@Test
public void testCreateWithIdFails() {
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails");
p.addName().addFamily("Hello");
p.setId("Patient/abc");
try {
myPatientDao.create(p, mySrd);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Can not create resource with ID[abc], ID must not be supplied"));
}
}
@Test
public void testCreateBundleAllowsDocumentAndCollection() {
String methodName = "testCreateBundleAllowsDocumentAndCollection";

View File

@ -70,6 +70,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou
//@formatter:off
return new RuleBuilder()
.allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/" + output1.getId().getIdPart())).andThen()
.allow().updateConditional().allResources()
.build();
//@formatter:on
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsInRelativeOrder;
@ -95,9 +96,11 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.NestedExceptionUtils;
import com.google.common.collect.Lists;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.parser.IParser;
@ -128,6 +131,47 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
}
@Test
public void testSearchPagingKeepsOldSearches() throws Exception {
String methodName = "testSearchPagingKeepsOldSearches";
IIdType pid1;
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("0");
patient.addName().addFamily(methodName).addGiven("Joe");
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
for (int i = 1; i <= 20; i++) {
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue(Integer.toString(i));
patient.addName().addFamily(methodName).addGiven("Joe");
myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
List<String> linkNext = Lists.newArrayList();
for (int i = 0 ; i < 100; i++) {
Bundle bundle = ourClient
.search()
.forResource(Patient.class)
.where(Patient.NAME.matches().value("testSearchPagingKeepsOldSearches"))
.count(5)
.returnBundle(Bundle.class)
.execute();
assertTrue(isNotBlank(bundle.getLink("next").getUrl()));
assertEquals(5, bundle.getEntry().size());
linkNext.add(bundle.getLink("next").getUrl());
}
int index = 0;
for (String nextLink : linkNext) {
ourLog.info("Fetching index {}", index++);
Bundle b = ourClient.fetchResourceFromUrl(Bundle.class, nextLink);
assertEquals(5, b.getEntry().size());
}
}
@Test
public void testHasParameter() throws Exception {
IIdType pid0;
@ -570,6 +614,107 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
}
@Test
public void testIdAndVersionInBodyForCreate() throws IOException {
String methodName = "testIdAndVersionInBodyForCreate";
Patient pt = new Patient();
pt.setId("Patient/AAA/_history/4");
pt.addName().addFamily(methodName);
String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt);
ourLog.info("Input: {}", resource);
HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
IdType id;
try {
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", respString);
assertEquals(201, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id = new IdType(newIdString);
} finally {
response.close();
}
assertEquals("1", id.getVersionIdPart());
assertNotEquals("AAA", id.getIdPart());
HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart());
response = ourHttpClient.execute(get);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", respString);
assertThat(respString, containsString("<id value=\"" + id.getIdPart() + "\"/>"));
assertThat(respString, containsString("<versionId value=\"1\"/>"));
} finally {
response.close();
}
}
@Test
public void testIdAndVersionInBodyForUpdate() throws IOException {
String methodName = "testIdAndVersionInBodyForUpdate";
Patient pt = new Patient();
pt.setId("Patient/AAA/_history/4");
pt.addName().addFamily(methodName);
String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt);
ourLog.info("Input: {}", resource);
HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
IdType id;
try {
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", respString);
assertEquals(201, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id = new IdType(newIdString);
} finally {
response.close();
}
assertEquals("1", id.getVersionIdPart());
assertNotEquals("AAA", id.getIdPart());
HttpPut put = new HttpPut(ourServerBase + "/Patient/" + id.getIdPart() + "/_history/1");
put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
response = ourHttpClient.execute(put);
try {
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", respString);
assertEquals(200, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id = new IdType(newIdString);
} finally {
response.close();
}
assertEquals("2", id.getVersionIdPart());
assertNotEquals("AAA", id.getIdPart());
HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart());
response = ourHttpClient.execute(get);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", respString);
assertThat(respString, containsString("<id value=\"" + id.getIdPart() + "\"/>"));
assertThat(respString, containsString("<versionId value=\"2\"/>"));
} finally {
response.close();
}
}
@Test
public void testCreateResourceConditionalComplex() throws IOException {
Patient pt = new Patient();
@ -2493,9 +2638,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
put.addHeader("Accept", Constants.CT_FHIR_JSON);
CloseableHttpResponse response = ourHttpClient.execute(put);
try {
assertEquals(400, response.getStatusLine().getStatusCode());
OperationOutcome oo = myFhirCtx.newJsonParser().parseResource(OperationOutcome.class, new InputStreamReader(response.getEntity().getContent()));
assertEquals("Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"FOO\" does not match URL ID of \""+p1id.getIdPart()+"\"", oo.getIssue().get(0).getDiagnostics());
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
response.close();
}
@ -2722,16 +2865,15 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
pt.addName().addFamily(methodName);
String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt);
HttpPut post = new HttpPut(ourServerBase + "/Patient/2");
HttpPut post = new HttpPut(ourServerBase + "/Patient/A2");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseString);
assertEquals(400, response.getStatusLine().getStatusCode());
OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, responseString);
assertThat(oo.getIssue().get(0).getDiagnostics(), containsString(
"Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"333\" does not match URL ID of \"2\""));
assertEquals(201, response.getStatusLine().getStatusCode());
assertThat(responseString, containsString("/A2/"));
assertThat(responseString, not(containsString("333")));
} finally {
response.close();
}

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
@ -200,11 +201,10 @@ public class TestRestfulServer extends RestfulServer {
setServerAddressStrategy(new MyHardcodedServerAddressStrategy(baseUrl));
/*
* This is a simple paging strategy that keeps the last 10
* searches in memory
* Spool results to the database
*/
setPagingProvider(new FifoMemoryPagingProvider(10).setMaximumPageSize(500));
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
* Load interceptors for the server from Spring
*/

View File

@ -307,6 +307,8 @@ public abstract class BaseResource extends BaseElement implements IResource {
myId = (IdDt) theId;
} else if (theId != null) {
myId = new IdDt(theId.getValue());
} else {
myId = null;
}
return this;
}

View File

@ -0,0 +1,82 @@
package ca.uhn.fhir.parser;
import org.hl7.fhir.dstu3.model.BackboneElement;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.StringType;
import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.util.ElementUtil;
@ResourceDef(name = "Patient")
public class PatientWithCustomCompositeExtension extends Patient {
private static final long serialVersionUID = 1L;
/**
* A custom extension
*/
@Child(name = "foo")
@Extension(url="http://acme.org/fooParent", definedLocally = false, isModifier = false)
protected FooParentExtension fooParentExtension;
public FooParentExtension getFooParentExtension() {
return fooParentExtension;
}
@Override
public boolean isEmpty() {
return super.isEmpty() && ElementUtil.isEmpty(fooParentExtension);
}
public void setFooParentExtension(FooParentExtension theFooParentExtension) {
fooParentExtension = theFooParentExtension;
}
@Block
public static class FooParentExtension extends BackboneElement {
private static final long serialVersionUID = 4522090347756045145L;
@Child(name = "childA")
@Extension(url = "http://acme.org/fooChildA", definedLocally = false, isModifier = false)
private StringType myChildA;
@Child(name = "childB")
@Extension(url = "http://acme.org/fooChildB", definedLocally = false, isModifier = false)
private StringType myChildB;
@Override
public FooParentExtension copy() {
FooParentExtension copy = new FooParentExtension();
copy.myChildA = myChildA;
copy.myChildB = myChildB;
return copy;
}
@Override
public boolean isEmpty() {
return super.isEmpty() && ElementUtil.isEmpty(myChildA, myChildB);
}
public StringType getChildA() {
return myChildA;
}
public StringType getChildB() {
return myChildB;
}
public void setChildA(StringType theChildA) {
myChildA = theChildA;
}
public void setChildB(StringType theChildB) {
myChildB = theChildB;
}
}
}

View File

@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@ -34,66 +35,21 @@ import org.custommonkey.xmlunit.XMLUnit;
import org.hamcrest.collection.IsEmptyCollection;
import org.hamcrest.core.StringContains;
import org.hamcrest.text.StringContainsInOrder;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Address.AddressUse;
import org.hl7.fhir.dstu3.model.Address.AddressUseEnumFactory;
import org.hl7.fhir.dstu3.model.AllergyIntolerance;
import org.hl7.fhir.dstu3.model.Annotation;
import org.hl7.fhir.dstu3.model.Appointment;
import org.hl7.fhir.dstu3.model.AuditEvent;
import org.hl7.fhir.dstu3.model.Binary;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Communication;
import org.hl7.fhir.dstu3.model.Composition;
import org.hl7.fhir.dstu3.model.ConceptMap;
import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem;
import org.hl7.fhir.dstu3.model.DataElement;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.DiagnosticReport;
import org.hl7.fhir.dstu3.model.DiagnosticReport.DiagnosticReportStatus;
import org.hl7.fhir.dstu3.model.DocumentManifest;
import org.hl7.fhir.dstu3.model.Duration;
import org.hl7.fhir.dstu3.model.ElementDefinition;
import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.dstu3.model.Encounter;
import org.hl7.fhir.dstu3.model.EnumFactory;
import org.hl7.fhir.dstu3.model.Enumeration;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Enumerations.DocumentReferenceStatus;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.GuidanceResponse;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.HumanName.NameUse;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Identifier;
import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse;
import org.hl7.fhir.dstu3.model.InstantType;
import org.hl7.fhir.dstu3.model.Location;
import org.hl7.fhir.dstu3.model.Medication;
import org.hl7.fhir.dstu3.model.MedicationOrder;
import org.hl7.fhir.dstu3.model.MedicationStatement;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Observation.ObservationRelationshipType;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.Organization;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.PrimitiveType;
import org.hl7.fhir.dstu3.model.ProcedureRequest;
import org.hl7.fhir.dstu3.model.Quantity;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.SampledData;
import org.hl7.fhir.dstu3.model.SimpleQuantity;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.After;
import org.junit.AfterClass;
@ -110,6 +66,7 @@ import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.FooMessageHeaderWithExplicitField.FooMessageSourceComponent;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.parser.PatientWithCustomCompositeExtension.FooParentExtension;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.util.TestUtil;
@ -126,142 +83,6 @@ public class XmlParserDstu3Test {
ourCtx.setNarrativeGenerator(null);
}
@Test
public void testEncodeAndParseContainedCustomTypes() {
ourCtx = FhirContext.forDstu3();
ourCtx.setDefaultTypeForProfile(CustomObservation.PROFILE, CustomObservation.class);
ourCtx.setDefaultTypeForProfile(CustomDiagnosticReport.PROFILE, CustomDiagnosticReport.class);
CustomObservation obs = new CustomObservation();
obs.setStatus(ObservationStatus.FINAL);
CustomDiagnosticReport dr = new CustomDiagnosticReport();
dr.setStatus(DiagnosticReportStatus.FINAL);
dr.addResult().setResource(obs);
IParser parser = ourCtx.newXmlParser();
parser.setPrettyPrint(true);
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
//@formatter:off
assertThat(output,stringContainsInOrder(
"<DiagnosticReport xmlns=\"http://hl7.org/fhir\">",
"<meta>",
"<profile value=\"http://custom_DiagnosticReport\"/>",
"</meta>",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<meta>",
"<profile value=\"http://custom_Observation\"/>",
"</meta>",
"<status value=\"final\"/>",
"</Observation>",
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"</result>",
"</DiagnosticReport>"));
//@formatter:on
/*
* Now PARSE!
*/
dr = (CustomDiagnosticReport) parser.parseResource(output);
assertEquals(DiagnosticReportStatus.FINAL, dr.getStatus());
assertEquals("#1", dr.getResult().get(0).getReference());
obs = (CustomObservation) dr.getResult().get(0).getResource();
assertEquals(ObservationStatus.FINAL, obs.getStatus());
ourCtx = null;
}
@Test
public void testEncodeAndParseContainedNonCustomTypes() {
ourCtx = FhirContext.forDstu3();
Observation obs = new Observation();
obs.setStatus(ObservationStatus.FINAL);
DiagnosticReport dr = new DiagnosticReport();
dr.setStatus(DiagnosticReportStatus.FINAL);
dr.addResult().setResource(obs);
IParser parser = ourCtx.newXmlParser();
parser.setPrettyPrint(true);
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
//@formatter:off
assertThat(output,stringContainsInOrder(
"<DiagnosticReport xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<status value=\"final\"/>",
"</Observation>",
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"</result>",
"</DiagnosticReport>"));
//@formatter:on
/*
* Now PARSE!
*/
dr = (DiagnosticReport) parser.parseResource(output);
assertEquals(DiagnosticReportStatus.FINAL, dr.getStatus());
assertEquals("#1", dr.getResult().get(0).getReference());
obs = (Observation) dr.getResult().get(0).getResource();
assertEquals(ObservationStatus.FINAL, obs.getStatus());
ourCtx = null;
}
@Test
public void testEncodeHistoryEncodeVersionsAtPath3() {
ourCtx = FhirContext.forDstu3();
assertNull(ourCtx.newXmlParser().getStripVersionsFromReferences());
AuditEvent auditEvent = new AuditEvent();
auditEvent.addEntity().setReference(new Reference("http://foo.com/Organization/2/_history/1"));
IParser parser = ourCtx.newXmlParser();
parser.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference");
String enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2/_history/1\"/>"));
parser.setDontStripVersionsFromReferencesAtPaths(new ArrayList<String>());
enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2\"/>"));
parser.setDontStripVersionsFromReferencesAtPaths((String[])null);
enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2\"/>"));
parser.setDontStripVersionsFromReferencesAtPaths((List<String>)null);
enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2\"/>"));
}
@Test
public void testBundleWithBinary() {
//@formatter:off
@ -292,7 +113,7 @@ public class XmlParserDstu3Test {
assertArrayEquals(new byte[] { 1, 2, 3, 4 }, bin.getContent());
}
@Test
public void testContainedResourceInExtensionUndeclared() {
Patient p = new Patient();
@ -314,7 +135,7 @@ public class XmlParserDstu3Test {
o = (Organization) rr.getResource();
assertEquals("ORG", o.getName());
}
@Test
public void testDuration() {
Encounter enc = new Encounter();
@ -328,7 +149,7 @@ public class XmlParserDstu3Test {
assertThat(str, not(containsString("meta")));
assertThat(str, containsString("<length><value value=\"123\"/><unit value=\"day\"/></length>"));
}
@Test
public void testEncodeAndParseBundleWithResourceRefs() {
@ -366,6 +187,24 @@ public class XmlParserDstu3Test {
assertSame(org, pt.getManagingOrganization().getResource());
}
@Test
public void testEncodeAndParseCompositeExtension() {
PatientWithCustomCompositeExtension pat = new PatientWithCustomCompositeExtension();
pat.setId("123");
pat.setFooParentExtension(new FooParentExtension());
pat.getFooParentExtension().setChildA(new StringType("ValueA"));
pat.getFooParentExtension().setChildB(new StringType("ValueB"));
String enc = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pat);
ourLog.info(enc);
pat = ourCtx.newXmlParser().parseResource(PatientWithCustomCompositeExtension.class, enc);
assertEquals("ValueA", pat.getFooParentExtension().getChildA().getValue());
assertEquals("ValueB", pat.getFooParentExtension().getChildB().getValue());
}
@Test
public void testEncodeAndParseContained() {
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
@ -435,6 +274,108 @@ public class XmlParserDstu3Test {
}
@Test
public void testEncodeAndParseContainedCustomTypes() {
ourCtx = FhirContext.forDstu3();
ourCtx.setDefaultTypeForProfile(CustomObservation.PROFILE, CustomObservation.class);
ourCtx.setDefaultTypeForProfile(CustomDiagnosticReport.PROFILE, CustomDiagnosticReport.class);
CustomObservation obs = new CustomObservation();
obs.setStatus(ObservationStatus.FINAL);
CustomDiagnosticReport dr = new CustomDiagnosticReport();
dr.setStatus(DiagnosticReportStatus.FINAL);
dr.addResult().setResource(obs);
IParser parser = ourCtx.newXmlParser();
parser.setPrettyPrint(true);
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
//@formatter:off
assertThat(output,stringContainsInOrder(
"<DiagnosticReport xmlns=\"http://hl7.org/fhir\">",
"<meta>",
"<profile value=\"http://custom_DiagnosticReport\"/>",
"</meta>",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<meta>",
"<profile value=\"http://custom_Observation\"/>",
"</meta>",
"<status value=\"final\"/>",
"</Observation>",
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"</result>",
"</DiagnosticReport>"));
//@formatter:on
/*
* Now PARSE!
*/
dr = (CustomDiagnosticReport) parser.parseResource(output);
assertEquals(DiagnosticReportStatus.FINAL, dr.getStatus());
assertEquals("#1", dr.getResult().get(0).getReference());
obs = (CustomObservation) dr.getResult().get(0).getResource();
assertEquals(ObservationStatus.FINAL, obs.getStatus());
ourCtx = null;
}
@Test
public void testEncodeAndParseContainedNonCustomTypes() {
ourCtx = FhirContext.forDstu3();
Observation obs = new Observation();
obs.setStatus(ObservationStatus.FINAL);
DiagnosticReport dr = new DiagnosticReport();
dr.setStatus(DiagnosticReportStatus.FINAL);
dr.addResult().setResource(obs);
IParser parser = ourCtx.newXmlParser();
parser.setPrettyPrint(true);
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
//@formatter:off
assertThat(output,stringContainsInOrder(
"<DiagnosticReport xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<status value=\"final\"/>",
"</Observation>",
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"</result>",
"</DiagnosticReport>"));
//@formatter:on
/*
* Now PARSE!
*/
dr = (DiagnosticReport) parser.parseResource(output);
assertEquals(DiagnosticReportStatus.FINAL, dr.getStatus());
assertEquals("#1", dr.getResult().get(0).getReference());
obs = (Observation) dr.getResult().get(0).getResource();
assertEquals(ObservationStatus.FINAL, obs.getStatus());
ourCtx = null;
}
@Test
public void testEncodeAndParseExtensionOnCode() {
Organization o = new Organization();
@ -1140,6 +1081,30 @@ public class XmlParserDstu3Test {
ourLog.info(parser.encodeResourceToString(gr));
}
@Test
public void testEncodeDeclaredBlock() throws Exception {
FooMessageSourceComponent source = new FooMessageHeaderWithExplicitField.FooMessageSourceComponent();
source.getMessageHeaderApplicationId().setValue("APPID");
source.setName("NAME");
FooMessageHeaderWithExplicitField header = new FooMessageHeaderWithExplicitField();
header.setSourceNew(source);
header.addDestination().setName("DEST");
Bundle bundle = new Bundle();
bundle.addEntry().setResource(header);
IParser p = ourCtx.newXmlParser();
p.setPrettyPrint(true);
String encode = p.encodeResourceToString(bundle);
ourLog.info(encode);
assertThat(encode, containsString("<value value=\"APPID\"/>"));
assertThat(encode, stringContainsInOrder("<source", "<dest"));
}
/**
* Make sure whitespace is preserved for pre tags
*/
@ -1368,6 +1333,39 @@ public class XmlParserDstu3Test {
}
@Test
public void testEncodeHistoryEncodeVersionsAtPath3() {
ourCtx = FhirContext.forDstu3();
assertNull(ourCtx.newXmlParser().getStripVersionsFromReferences());
AuditEvent auditEvent = new AuditEvent();
auditEvent.addEntity().setReference(new Reference("http://foo.com/Organization/2/_history/1"));
IParser parser = ourCtx.newXmlParser();
parser.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference");
String enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2/_history/1\"/>"));
parser.setDontStripVersionsFromReferencesAtPaths(new ArrayList<String>());
enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2\"/>"));
parser.setDontStripVersionsFromReferencesAtPaths((String[])null);
enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2\"/>"));
parser.setDontStripVersionsFromReferencesAtPaths((List<String>)null);
enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2\"/>"));
}
@Test
public void testEncodeNarrativeSuppressed() {
Patient patient = new Patient();
@ -1498,6 +1496,23 @@ public class XmlParserDstu3Test {
assertThat(str, containsString("<reference value=\"Observation/phitcc_obs_bp_dia\"/>"));
}
@Test
public void testEncodeReferenceWithUuid() {
Practitioner pract = new Practitioner();
pract.setId(IdType.newRandomUuid());
pract.addName().addFamily("PRACT FAMILY");
Patient patient = new Patient();
patient.addGeneralPractitioner().setResource(pract);
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(pract.getId(), startsWith("urn:uuid:"));
assertThat(encoded, containsString("<reference value=\"" + pract.getId() + "\"/>"));
}
@Test
public void testEncodeSummary() {
Patient patient = new Patient();
@ -1561,30 +1576,6 @@ public class XmlParserDstu3Test {
assertThat(encode, stringContainsInOrder("<source", "<dest"));
}
@Test
public void testEncodeDeclaredBlock() throws Exception {
FooMessageSourceComponent source = new FooMessageHeaderWithExplicitField.FooMessageSourceComponent();
source.getMessageHeaderApplicationId().setValue("APPID");
source.setName("NAME");
FooMessageHeaderWithExplicitField header = new FooMessageHeaderWithExplicitField();
header.setSourceNew(source);
header.addDestination().setName("DEST");
Bundle bundle = new Bundle();
bundle.addEntry().setResource(header);
IParser p = ourCtx.newXmlParser();
p.setPrettyPrint(true);
String encode = p.encodeResourceToString(bundle);
ourLog.info(encode);
assertThat(encode, containsString("<value value=\"APPID\"/>"));
assertThat(encode, stringContainsInOrder("<source", "<dest"));
}
@Test
public void testEncodeUndeclaredExtensionWithEnumerationContent() {
IParser parser = ourCtx.newXmlParser();

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
@ -25,7 +27,6 @@ import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
@ -52,7 +53,7 @@ public class UpdateDstu3Test {
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
@ -78,7 +79,7 @@ public class UpdateDstu3Test {
CloseableHttpResponse status = ourClient.execute(httpPost);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -101,7 +102,7 @@ public class UpdateDstu3Test {
CloseableHttpResponse status = ourClient.execute(httpPost);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -117,28 +118,22 @@ public class UpdateDstu3Test {
public void testUpdateWrongUrlInBody() throws Exception {
Patient patient = new Patient();
patient.setId("3");
patient.setId("Patient/3/_history/4");
patient.addIdentifier().setValue("002");
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/001");
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/1/_history/2");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
OperationOutcome oo = ourCtx.newXmlParser().parseResource(OperationOutcome.class, responseContent);
assertEquals(
"Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"3\" does not match URL ID of \"001\"",
oo.getIssue().get(0).getDiagnostics());
assertEquals(400, status.getStatusLine().getStatusCode());
assertNull(status.getFirstHeader("location"));
assertNull(status.getFirstHeader("content-location"));
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/1/_history/002", status.getFirstHeader("location").getValue());
assertEquals("Patient/1/_history/2", ourId.getValue());
}
@AfterClass

View File

@ -165,6 +165,26 @@
Inprove handling of _text and _content searches in JPA server to do better
matching on partial strings
</action>
<action type="add">
Servers in STU3 mode will now ignore any ID or VersionID found in the
resource body provided by the client when processing FHIR
<![CDATA[<code>update</code>]]> operations. This change has been made
because the FHIR specification now requires servers to ignore
these values. Note that as a result of this change, resources passed
to <![CDATA[<code>@Update</code>]]> methods will always have
<![CDATA[<code>null</code>]]> ID
</action>
<action type="add">
Add new methods to
<![CDATA[<code>AuthorizationInterceptor</code>]]>
which allow user code to declare support for conditional
create, update, and delete.
</action>
<action type="fix">
When encoding a resource with a reference to another resource
that has a placeholder ID (e.g. urn:uuid:foo), the urn prefix
was incorrectly stripped from the reference.
</action>
</release>
<release version="1.6" date="2016-07-07">
<action type="fix">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -240,6 +240,36 @@
</macro>
</subsection>
<subsection name="Custom Type Examples: Composite Extensions">
<p>
The following example shows a resource containing a composite
extension.
</p>
<macro name="snippet">
<param name="id" value="resource" />
<param name="file" value="examples/src/main/java/example/customtype/CustomCompositeExtension.java" />
</macro>
<p>
This could be used to create a resource such as the
following:
</p>
<source><![CDATA[<Patient xmlns="http://hl7.org/fhir">
<id value="123"/>
<extension url="http://acme.org/fooParent">
<extension url="http://acme.org/fooChildA">
<valueString value="ValueA"/>
</extension>
<extension url="http://acme.org/fooChildB">
<valueString value="ValueB"/>
</extension>
</extension>
</Patient>]]></source>
</subsection>
</section>

View File

@ -91,8 +91,10 @@
<p>
This interceptor can help with the complicated task of determining whether a user
has the appropriate permission to perform a given task on a FHIR server. This is
done by declaring
done by declaring a set of rules that can selectively allow (whitelist) and/or selectively
block (blacklist) requests.
</p>
<p class="doc_info_bubble">
AuthorizationInterceptor is a new feature in HAPI FHIR, and has not yet
been heavily tested. Use with caution, and do lots of testing! We welcome