Merge branch 'master' of github.com:jamesagnew/hapi-fhir
This commit is contained in:
commit
35a890db34
|
@ -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
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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,11 +34,17 @@ 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) {
|
||||
|
@ -48,22 +58,32 @@ public class HapiLocalizer {
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -297,6 +334,13 @@ public abstract class RequestDetails {
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
public interface IAuthRuleBuilderRuleConditional extends IAuthRuleBuilderAppliesTo<IAuthRuleBuilderRuleConditionalClassifier> {
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -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,21 +115,36 @@ 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;
|
||||
|
@ -138,12 +157,58 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
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;
|
||||
|
@ -171,7 +236,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
private IAuthRuleBuilderRuleOpClassifierFinished finished() {
|
||||
|
||||
Rule rule = new Rule(myRuleName);
|
||||
RuleImplOp rule = new RuleImplOp(myRuleName);
|
||||
rule.setMode(myRuleMode);
|
||||
rule.setOp(myRuleOp);
|
||||
rule.setAppliesTo(myAppliesTo);
|
||||
|
@ -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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
public IAuthRuleBuilderOperationNamed named(String theOperationName) {
|
||||
Validate.notBlank(theOperationName, "theOperationName must not be null or empty");
|
||||
return new RuleBuilderRuleOperationNamed(theOperationName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
|
||||
@Override
|
||||
public IAuthRuleBuilderOperationNamed withAnyName() {
|
||||
return new RuleBuilderRuleOperationNamed(null);
|
||||
}
|
||||
|
||||
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,18 +319,6 @@ 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");
|
||||
|
@ -299,25 +333,53 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
return new RuleBuilderFinished();
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished onServer() {
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToServer();
|
||||
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 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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("");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ public class DatabaseBackedPagingProvider extends FifoMemoryPagingProvider {
|
|||
if (!provider.ensureSearchEntityLoaded()) {
|
||||
return null;
|
||||
}
|
||||
return provider;
|
||||
retVal = provider;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,10 +201,9 @@ 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -241,6 +241,36 @@
|
|||
|
||||
</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>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue