Merge branch 'master' into 2515_MDM_survivorship_rules

This commit is contained in:
Nick Goupinets 2021-03-31 16:07:45 -04:00
commit 6b4d6702c1
75 changed files with 6291 additions and 2419 deletions

View File

@ -148,6 +148,12 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
return toList(provideStructureDefinitionMap());
}
@Nullable
@Override
public <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
return null;
}
@Override
public IBaseResource fetchCodeSystem(String theSystem) {

View File

@ -36,11 +36,13 @@ import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
@ -109,6 +111,32 @@ public interface IValidationSupport {
return null;
}
/**
* Load and return all possible structure definitions aside from resource definitions themselves
*/
@Nullable
default <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
List<T> retVal = fetchAllStructureDefinitions();
if (retVal != null) {
List<T> newList = new ArrayList<>(retVal.size());
for (T next : retVal) {
String url = defaultString(getFhirContext().newTerser().getSinglePrimitiveValueOrNull(next, "url"));
if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
String lastPart = url.substring("http://hl7.org/fhir/StructureDefinition/".length());
if (getFhirContext().getResourceTypes().contains(lastPart)) {
continue;
}
}
newList.add(next);
}
retVal = newList;
}
return retVal;
}
/**
* Fetch a code system by ID
*

View File

@ -222,6 +222,53 @@ public enum Pointcut implements IPointcut {
"ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException"
),
/**
* <b>Server Hook:</b>
* This method is immediately before the handling method is selected. Interceptors may make changes
* to the request that can influence which handler will ultimately be called.
* <p>
* Hooks may accept the following parameters:
* <ul>
* <li>
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request.
* Note that the bean properties are not all guaranteed to be populated at the time this hook is called.
* </li>
* <li>
* ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
* only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
* </li>
* <li>
* javax.servlet.http.HttpServletRequest - The servlet request, when running in a servlet environment
* </li>
* <li>
* javax.servlet.http.HttpServletResponse - The servlet response, when running in a servlet environment
* </li>
* </ul>
* <p>
* Hook methods may return <code>true</code> or <code>void</code> if processing should continue normally.
* This is generally the right thing to do.
* If your interceptor is providing an HTTP response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* </p>
* <p>
* Hook methods may also throw {@link AuthenticationException} if they would like. This exception may be thrown
* to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*
* @since 5.4.0
*/
SERVER_INCOMING_REQUEST_PRE_HANDLER_SELECTED(boolean.class,
"ca.uhn.fhir.rest.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails",
"javax.servlet.http.HttpServletRequest",
"javax.servlet.http.HttpServletResponse"
),
/**
* <b>Server Hook:</b>
* This method is called just before the actual implementing server method is invoked.

View File

@ -55,9 +55,18 @@ public @interface Read {
// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
Class<? extends IBaseResource> type() default IBaseResource.class;
/**
* This method allows the return type for this method to be specified in a
* non-type-specific way, using the text name of the resource, e.g. "Patient".
*
* This attribute should be populate, or {@link #type()} should be, but not both.
*
* @since 5.4.0
*/
String typeName() default "";
/**
* If set to true (default is false), this method supports vread operation as well as read
*/
boolean version() default false;
}

View File

@ -131,6 +131,9 @@ public class Constants {
public static final String HEADER_LOCATION_LC = HEADER_LOCATION.toLowerCase();
public static final String HEADER_ORIGIN = "Origin";
public static final String HEADER_PREFER = "Prefer";
public static final String HEADER_PREFER_HANDLING = "handling";
public static final String HEADER_PREFER_HANDLING_STRICT = "strict";
public static final String HEADER_PREFER_HANDLING_LENIENT = "lenient";
public static final String HEADER_PREFER_RETURN = "return";
public static final String HEADER_PREFER_RETURN_MINIMAL = "minimal";
public static final String HEADER_PREFER_RETURN_REPRESENTATION = "representation";

View File

@ -0,0 +1,54 @@
package ca.uhn.fhir.rest.api;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* 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 java.util.HashMap;
/**
* Represents values for "handling" value as provided in the the <a href="http://hl7.org/fhir/search.html">FHIR Search Spec</a>.
*/
public enum PreferHandlingEnum {
STRICT(Constants.HEADER_PREFER_HANDLING_STRICT), LENIENT(Constants.HEADER_PREFER_HANDLING_LENIENT);
private static HashMap<String, PreferHandlingEnum> ourValues;
private String myHeaderValue;
PreferHandlingEnum(String theHeaderValue) {
myHeaderValue = theHeaderValue;
}
public String getHeaderValue() {
return myHeaderValue;
}
public static PreferHandlingEnum fromHeaderValue(String theHeaderValue) {
if (ourValues == null) {
HashMap<String, PreferHandlingEnum> values = new HashMap<>();
for (PreferHandlingEnum next : PreferHandlingEnum.values()) {
values.put(next.getHeaderValue(), next);
}
ourValues = values;
}
return ourValues.get(theHeaderValue);
}
}

View File

@ -26,9 +26,10 @@ public class PreferHeader {
private PreferReturnEnum myReturn;
private boolean myRespondAsync;
private PreferHandlingEnum myHanding;
public @Nullable
PreferReturnEnum getReturn() {
@Nullable
public PreferReturnEnum getReturn() {
return myReturn;
}
@ -46,4 +47,12 @@ public class PreferHeader {
return this;
}
@Nullable
public PreferHandlingEnum getHanding() {
return myHanding;
}
public void setHanding(PreferHandlingEnum theHanding) {
myHanding = theHanding;
}
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.api;
* #L%
*/
import javax.annotation.Nullable;
import java.util.HashMap;
/**
@ -27,7 +28,7 @@ import java.util.HashMap;
*/
public enum PreferReturnEnum {
REPRESENTATION("representation"), MINIMAL("minimal"), OPERATION_OUTCOME("OperationOutcome");
REPRESENTATION(Constants.HEADER_PREFER_RETURN_REPRESENTATION), MINIMAL(Constants.HEADER_PREFER_RETURN_MINIMAL), OPERATION_OUTCOME(Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME);
private static HashMap<String, PreferReturnEnum> ourValues;
private String myHeaderValue;
@ -40,6 +41,7 @@ public enum PreferReturnEnum {
return myHeaderValue;
}
@Nullable
public static PreferReturnEnum fromHeaderValue(String theHeaderValue) {
if (ourValues == null) {
HashMap<String, PreferReturnEnum> values = new HashMap<>();

View File

@ -25,17 +25,22 @@ import java.util.HashMap;
import java.util.Map;
import ca.uhn.fhir.util.CoverageIgnore;
import org.apache.commons.lang3.Validate;
import javax.annotation.Nonnull;
@CoverageIgnore
public enum RestOperationTypeEnum {
ADD_TAGS("add-tags"),
BATCH("batch", true, false, false),
DELETE_TAGS("delete-tags"),
ADD_TAGS("add-tags", false, false, true),
GET_TAGS("get-tags"),
DELETE_TAGS("delete-tags", false, false, true),
GET_PAGE("get-page"),
GET_TAGS("get-tags", false, true, true),
GET_PAGE("get-page", false, false, false),
/**
* <b>
@ -43,111 +48,111 @@ public enum RestOperationTypeEnum {
* change as the GraphQL interface matures
* </b>
*/
GRAPHQL_REQUEST("graphql-request"),
GRAPHQL_REQUEST("graphql-request", false, false, false),
/**
* E.g. $everything, $validate, etc.
*/
EXTENDED_OPERATION_SERVER("extended-operation-server"),
EXTENDED_OPERATION_SERVER("extended-operation-server", false, false, false),
/**
* E.g. $everything, $validate, etc.
*/
EXTENDED_OPERATION_TYPE("extended-operation-type"),
EXTENDED_OPERATION_TYPE("extended-operation-type", false, false, false),
/**
* E.g. $everything, $validate, etc.
*/
EXTENDED_OPERATION_INSTANCE("extended-operation-instance"),
EXTENDED_OPERATION_INSTANCE("extended-operation-instance", false, false, false),
/**
* Code Value: <b>create</b>
*/
CREATE("create"),
CREATE("create", false, true, false),
/**
* Code Value: <b>delete</b>
*/
DELETE("delete"),
DELETE("delete", false, false, true),
/**
* Code Value: <b>history-instance</b>
*/
HISTORY_INSTANCE("history-instance"),
HISTORY_INSTANCE("history-instance", false, false, true),
/**
* Code Value: <b>history-system</b>
*/
HISTORY_SYSTEM("history-system"),
HISTORY_SYSTEM("history-system", true, false, false),
/**
* Code Value: <b>history-type</b>
*/
HISTORY_TYPE("history-type"),
HISTORY_TYPE("history-type", false, true, false),
/**
* Code Value: <b>read</b>
*/
READ("read"),
READ("read", false, false, true),
/**
* Code Value: <b>search-system</b>
*/
SEARCH_SYSTEM("search-system"),
SEARCH_SYSTEM("search-system", true, false, false),
/**
* Code Value: <b>search-type</b>
*/
SEARCH_TYPE("search-type"),
SEARCH_TYPE("search-type", false, true, false),
/**
* Code Value: <b>transaction</b>
*/
TRANSACTION("transaction"),
TRANSACTION("transaction", true, false, false),
/**
* Code Value: <b>update</b>
*/
UPDATE("update"),
UPDATE("update", false, false, true),
/**
* Code Value: <b>validate</b>
*/
VALIDATE("validate"),
VALIDATE("validate", false, true, true),
/**
* Code Value: <b>vread</b>
*/
VREAD("vread"),
VREAD("vread", false, false, true),
/**
* Load the server's metadata
*/
METADATA("metadata"),
METADATA("metadata", false, false, false),
/**
* $meta-add extended operation
*/
META_ADD("$meta-add"),
META_ADD("$meta-add", false, false, false),
/**
* $meta-add extended operation
*/
META("$meta"),
META("$meta", false, false, false),
/**
* $meta-delete extended operation
*/
META_DELETE("$meta-delete"),
META_DELETE("$meta-delete", false, false, false),
/**
* Patch operation
*/
PATCH("patch"),
PATCH("patch", false, false, true),
;
private static Map<String, RestOperationTypeEnum> CODE_TO_ENUM = new HashMap<String, RestOperationTypeEnum>();
private static final Map<String, RestOperationTypeEnum> CODE_TO_ENUM = new HashMap<String, RestOperationTypeEnum>();
/**
* Identifier for this Value Set: http://hl7.org/fhir/vs/type-restful-operation
@ -166,27 +171,45 @@ public enum RestOperationTypeEnum {
}
private final String myCode;
private final boolean mySystemLevel;
private final boolean myTypeLevel;
private final boolean myInstanceLevel;
/**
* Constructor
*/
RestOperationTypeEnum(String theCode) {
RestOperationTypeEnum(@Nonnull String theCode, boolean theSystemLevel, boolean theTypeLevel, boolean theInstanceLevel) {
myCode = theCode;
mySystemLevel = theSystemLevel;
myTypeLevel = theTypeLevel;
myInstanceLevel = theInstanceLevel;
}
/**
* Returns the enumerated value associated with this code
*/
public RestOperationTypeEnum forCode(String theCode) {
RestOperationTypeEnum retVal = CODE_TO_ENUM.get(theCode);
return retVal;
public RestOperationTypeEnum forCode(@Nonnull String theCode) {
Validate.notNull(theCode, "theCode must not be null");
return CODE_TO_ENUM.get(theCode);
}
/**
* Returns the code associated with this enumerated value
*/
@Nonnull
public String getCode() {
return myCode;
}
public boolean isSystemLevel() {
return mySystemLevel;
}
public boolean isTypeLevel() {
return myTypeLevel;
}
public boolean isInstanceLevel() {
return myInstanceLevel;
}
}

View File

@ -61,7 +61,9 @@ public class ClasspathUtil {
public static InputStream loadResourceAsStream(String theClasspath) {
InputStream retVal = ClasspathUtil.class.getResourceAsStream(theClasspath);
if (retVal == null) {
if (!theClasspath.startsWith("/")) {
if (theClasspath.startsWith("/")) {
retVal = ClasspathUtil.class.getResourceAsStream(theClasspath.substring(1));
} else {
retVal = ClasspathUtil.class.getResourceAsStream("/" + theClasspath);
}
if (retVal == null) {

View File

@ -21,6 +21,7 @@ import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
@ -34,6 +35,8 @@ import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -42,6 +45,7 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -317,6 +321,14 @@ public class FhirTerser {
return retVal.get(0);
}
public Optional<String> getSinglePrimitiveValue(IBase theTarget, String thePath) {
return getSingleValue(theTarget, thePath, IPrimitiveType.class).map(t->t.getValueAsString());
}
public String getSinglePrimitiveValueOrNull(IBase theTarget, String thePath) {
return getSingleValue(theTarget, thePath, IPrimitiveType.class).map(t->t.getValueAsString()).orElse(null);
}
public <T extends IBase> Optional<T> getSingleValue(IBase theTarget, String thePath, Class<T> theWantedType) {
return Optional.ofNullable(getSingleValueOrNull(theTarget, thePath, theWantedType));
}
@ -671,10 +683,8 @@ public class FhirTerser {
parts.add(thePath.substring(currentStart));
if (theElementDef instanceof RuntimeResourceDefinition) {
if (parts.size() > 0 && parts.get(0).equals(theElementDef.getName())) {
parts = parts.subList(1, parts.size());
}
if (parts.size() > 0 && parts.get(0).equals(theElementDef.getName())) {
parts = parts.subList(1, parts.size());
}
if (parts.size() < 1) {
@ -1168,6 +1178,194 @@ public class FhirTerser {
return containedResources;
}
/**
* Adds and returns a new element at the given path within the given structure. The paths used here
* are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions.
* <p>
* Only the last entry in the path is always created, existing repetitions of elements before
* the final dot are returned if they exists (although they are created if they do not). For example,
* given the path <code>Patient.name.given</code>, a new repetition of <code>given</code> is always
* added to the first (index 0) repetition of the name. If an index-0 repetition of <code>name</code>
* already exists, it is added to. If one does not exist, it if created and then added to.
* </p>
* <p>
* If the last element in the path refers to a non-repeatable element that is already present and
* is not empty, a {@link DataFormatException} error will be thrown.
* </p>
*
* @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
* instance, but does not need to be.
* @param thePath The path.
* @return The newly added element
* @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or
* an element that is non-repeatable but not already populated.
*/
@SuppressWarnings("unchecked")
@Nonnull
public <T extends IBase> T addElement(@Nonnull IBase theTarget, @Nonnull String thePath) {
return (T) doAddElement(theTarget, thePath, 1).get(0);
}
@SuppressWarnings("unchecked")
private <T extends IBase> List<T> doAddElement(IBase theTarget, String thePath, int theElementsToAdd) {
if (theElementsToAdd == 0) {
return Collections.emptyList();
}
IBase target = theTarget;
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(target.getClass());
List<String> parts = parsePath(def, thePath);
for (int i = 0, partsSize = parts.size(); ; i++) {
String nextPart = parts.get(i);
boolean lastPart = i == partsSize - 1;
BaseRuntimeChildDefinition nextChild = def.getChildByName(nextPart);
if (nextChild == null) {
throw new DataFormatException("Invalid path " + thePath + ": Element of type " + def.getName() + " has no child named " + nextPart + ". Valid names: " + def.getChildrenAndExtension().stream().map(t -> t.getElementName()).sorted().collect(Collectors.joining(", ")));
}
List<IBase> childValues = nextChild.getAccessor().getValues(target);
IBase childValue;
if (childValues.size() > 0 && !lastPart) {
childValue = childValues.get(0);
} else {
if (lastPart) {
if (!childValues.isEmpty()) {
if (theElementsToAdd == -1) {
return (List<T>) Collections.singletonList(childValues.get(0));
} else if (nextChild.getMax() == 1 && !childValues.get(0).isEmpty()) {
throw new DataFormatException("Element at path " + thePath + " is not repeatable and not empty");
} else if (nextChild.getMax() == 1 && childValues.get(0).isEmpty()) {
return (List<T>) Collections.singletonList(childValues.get(0));
}
}
}
BaseRuntimeElementDefinition<?> elementDef = nextChild.getChildByName(nextPart);
childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments());
nextChild.getMutator().addValue(target, childValue);
if (lastPart) {
if (theElementsToAdd == 1 || theElementsToAdd == -1) {
return (List<T>) Collections.singletonList(childValue);
} else {
if (nextChild.getMax() == 1) {
throw new DataFormatException("Can not add multiple values at path " + thePath + ": Element does not repeat");
}
List<T> values = (List<T>) Lists.newArrayList(childValue);
for (int j = 1; j < theElementsToAdd; j++) {
childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments());
nextChild.getMutator().addValue(target, childValue);
values.add((T) childValue);
}
return values;
}
}
}
target = childValue;
if (!lastPart) {
BaseRuntimeElementDefinition<?> nextDef = myContext.getElementDefinition(target.getClass());
if (!(nextDef instanceof BaseRuntimeElementCompositeDefinition)) {
throw new DataFormatException("Invalid path " + thePath + ": Element of type " + def.getName() + " has no child named " + nextPart + " (this is a primitive type)");
}
def = (BaseRuntimeElementCompositeDefinition<?>) nextDef;
}
}
}
/**
* Adds and returns a new element at the given path within the given structure. The paths used here
* are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions.
* <p>
* This method follows all of the same semantics as {@link #addElement(IBase, String)} but it
* requires the path to point to an element with a primitive datatype and set the value of
* the datatype to the given value.
* </p>
*
* @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
* instance, but does not need to be.
* @param thePath The path.
* @param theValue The value to set, or <code>null</code>.
* @return The newly added element
* @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or
* an element that is non-repeatable but not already populated.
*/
@SuppressWarnings("unchecked")
@Nonnull
public <T extends IBase> T addElement(@Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) {
T value = (T) doAddElement(theTarget, thePath, 1).get(0);
if (!(value instanceof IPrimitiveType)) {
throw new DataFormatException("Element at path " + thePath + " is not a primitive datatype. Found: " + myContext.getElementDefinition(value.getClass()).getName());
}
((IPrimitiveType<?>) value).setValueAsString(theValue);
return value;
}
/**
* Adds and returns a new element at the given path within the given structure. The paths used here
* are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions.
* <p>
* This method follows all of the same semantics as {@link #addElement(IBase, String)} but it
* requires the path to point to an element with a primitive datatype and set the value of
* the datatype to the given value.
* </p>
*
* @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
* instance, but does not need to be.
* @param thePath The path.
* @param theValue The value to set, or <code>null</code>.
* @return The newly added element
* @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or
* an element that is non-repeatable but not already populated.
*/
@SuppressWarnings("unchecked")
@Nonnull
public <T extends IBase> T setElement(@Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) {
T value = (T) doAddElement(theTarget, thePath, -1).get(0);
if (!(value instanceof IPrimitiveType)) {
throw new DataFormatException("Element at path " + thePath + " is not a primitive datatype. Found: " + myContext.getElementDefinition(value.getClass()).getName());
}
((IPrimitiveType<?>) value).setValueAsString(theValue);
return value;
}
/**
* This method has the same semantics as {@link #addElement(IBase, String, String)} but adds
* a collection of primitives instead of a single one.
*
* @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
* instance, but does not need to be.
* @param thePath The path.
* @param theValues The values to set, or <code>null</code>.
*/
public void addElements(IBase theTarget, String thePath, Collection<String> theValues) {
List<IBase> targets = doAddElement(theTarget, thePath, theValues.size());
Iterator<String> valuesIter = theValues.iterator();
for (IBase target : targets) {
if (!(target instanceof IPrimitiveType)) {
throw new DataFormatException("Element at path " + thePath + " is not a primitive datatype. Found: " + myContext.getElementDefinition(target.getClass()).getName());
}
((IPrimitiveType<?>) target).setValueAsString(valuesIter.next());
}
}
public enum OptionsEnum {

View File

@ -94,7 +94,7 @@ public class SchemaBaseValidator implements IValidatorModule {
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (SAXNotRecognizedException ex) {
ourLog.warn("Jaxp 1.5 Support not found.", ex);
ourLog.debug("Jaxp 1.5 Support not found.", ex);
}
validator.validate(new StreamSource(new StringReader(encodedResource)));

View File

@ -22,12 +22,12 @@ package ca.uhn.fhir.jpa.demo;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider;
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
@ -35,7 +35,7 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
@ -135,8 +135,9 @@ public class JpaServerDemo extends RestfulServer {
} else if (fhirVersion == FhirVersionEnum.R4) {
IFhirSystemDao<org.hl7.fhir.r4.model.Bundle, org.hl7.fhir.r4.model.Meta> systemDao = myAppCtx
.getBean("mySystemDaoR4", IFhirSystemDao.class);
JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao,
myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class));
IValidationSupport validationSupport = myAppCtx.getBean(IValidationSupport.class);
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(this, systemDao,
myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class), validationSupport);
confProvider.setImplementationDescription("Example Server");
setServerConformanceProvider(confProvider);
} else {

View File

@ -41,6 +41,8 @@ import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.util.ReflectionUtil;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class);

View File

@ -22,6 +22,7 @@ package ca.uhn.hapi.fhir.docs;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.rest.api.PreferHandlingEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.*;
@ -245,4 +246,28 @@ public class ServletExamples {
}
}
// START SNIPPET: preferHandling
@WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
public class RestfulServerWithPreferHandling extends RestfulServer {
@Override
protected void initialize() throws ServletException {
// Create an interceptor
SearchPreferHandlingInterceptor interceptor = new SearchPreferHandlingInterceptor();
// Optionally you can change the default behaviour for when the Prefer
// header is not found in the request or does not have a handling
// directive
interceptor.setDefaultBehaviour(PreferHandlingEnum.LENIENT);
// Register the interceptor
registerInterceptor(interceptor);
}
// END SNIPPET: preferHandling
}
}

View File

@ -0,0 +1,6 @@
---
type: add
issue: 2499
title: "When using the _mdm parameter during Group Bulk Export, resources written out will now contain an extension with the
url `https://hapifhir.org/associated-patient-golden-resource/` identifying which golden resource the target resource belongs to."

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2506
title: "A new server interceptor has been added that allows servers to implement lenient search mode,
where unknown search parameters are ignored if an optional HTTP Prefer header is provided."

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2506
title: "The server generated CapabilityStatment will now include supported Profile declarations
for FHIR R4+."

View File

@ -151,6 +151,24 @@ The following example shows how to register this interceptor within a HAPI FHIR
**See Also:** The [Repository Validating Interceptor](/docs/validation/repository_validating_interceptor.html) provides a different and potentially more powerful way of validating data when paired with a HAPI FHIR JPA Server.
<a name="lenient_searching"/>
# Search: Allow Lenient Searching
By default, HAPI FHIR applies strict search parameter validation. This means that FHIR search requests will fail if the search contains search parameters (any parameter that does not begin with an underscore) that are not known to the server.
* [SearchPreferHandlingInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/SearchPreferHandlingInterceptor.html)
* [SearchPreferHandlingInterceptor Source](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/SearchPreferHandlingInterceptor.java)
The SearchPreferHandlingInterceptor looks for a header of the form `Prefer: handling=lenient` or `Prefer: handling=strict` as described in the [FHIR Search Specification](http://hl7.org/fhir/search.html#errors) and treats it appropriately. A non-strict can also optionally be set.
The following example shows how to register this interceptor within a HAPI FHIR REST server.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|preferHandling}}
```
# Security: CORS
HAPI FHIR includes an interceptor which can be used to implement CORS support on your server. See [Server CORS Documentation](/docs/security/cors.html#cors_interceptor) for information on how to use this interceptor.
@ -281,7 +299,7 @@ The RepositoryValidatingInterceptor can be used to enforce validation rules on d
# Data Standardization
```StandardizingInterceptor``` handles data standardization (s13n) requirements. This interceptor applies standardization rules on all FHIR primitives as configured in the ```s13n.json``` file that should be made available on the classpath. This file contains FHIRPath definitions together with the standardizers that should be applied to that path. It comes with six per-build standardizers: NAME_FAMILY, NAME_GIVEN, EMAIL, TITLE, PHONE and TEXT. Custom standardizers can be developed by implementing ```ca.uhn.fhir.rest.server.interceptor.s13n.standardizers.IStandardizer``` interface.
`StandardizingInterceptor` handles data standardization (s13n) requirements. This interceptor applies standardization rules on all FHIR primitives as configured in the `s13n.json` file that should be made available on the classpath. This file contains FHIRPath definitions together with the standardizers that should be applied to that path. It comes with six per-build standardizers: NAME_FAMILY, NAME_GIVEN, EMAIL, TITLE, PHONE and TEXT. Custom standardizers can be developed by implementing `ca.uhn.fhir.rest.server.interceptor.s13n.standardizers.IStandardizer` interface.
A sample configuration file can be found below:
@ -303,20 +321,20 @@ A sample configuration file can be found below:
}
```
Standardization can be disabled for a given request by providing ```HAPI-Standardization-Disabled: *``` request header. Header value can be any string, it is the presence of the header that disables the s13n.
Standardization can be disabled for a given request by providing `HAPI-Standardization-Disabled: *` request header. Header value can be any string, it is the presence of the header that disables the s13n.
# Validation: Address Validation
```AddressValidatingInterceptor``` takes care of validation of addresses on all incoming resources through a 3rd party address validation service. Before a resource containing an Address field is stored, this interceptor invokes address validation service and then stores validation results as an extension on the address with ```https://hapifhir.org/AddressValidation/``` URL.
`AddressValidatingInterceptor` takes care of validation of addresses on all incoming resources through a 3rd party address validation service. Before a resource containing an Address field is stored, this interceptor invokes address validation service and then stores validation results as an extension on the address with `https://hapifhir.org/AddressValidation/` URL.
This interceptor is configured in ```address-validation.properties``` file that should be made available on the classpath. This file must contain ```validator.class``` property, which defines a fully qualified class implementing ```ca.uhn.fhir.rest.server.interceptor.validation.address.IAddressValidator``` interface. The specified implementation must provide service-specific logic for validating an Address instance. An example implementation can be found in ```ca.uhn.fhir.rest.server.interceptor.validation.address.impl.LoquateAddressValidator``` class which validates addresses by using Loquate Data Cleanse service.
This interceptor is configured in `address-validation.properties` file that should be made available on the classpath. This file must contain `validator.class` property, which defines a fully qualified class implementing `ca.uhn.fhir.rest.server.interceptor.validation.address.IAddressValidator` interface. The specified implementation must provide service-specific logic for validating an Address instance. An example implementation can be found in `ca.uhn.fhir.rest.server.interceptor.validation.address.impl.LoquateAddressValidator` class which validates addresses by using Loquate Data Cleanse service.
Address validation can be disabled for a given request by providing ```HAPI-Address-Validation-Disabled: *``` request header. Header value can be any string, it is the presence of the header that disables the validation.
Address validation can be disabled for a given request by providing `HAPI-Address-Validation-Disabled: *` request header. Header value can be any string, it is the presence of the header that disables the validation.
# Validation: Field-Level Validation
```FieldValidatingInterceptor``` allows validating primitive fields on various FHIR resources. It expects validation rules to be provided via ```field-validation-rules.json``` file that should be available on the classpath. JSON in this file defines a mapping of FHIRPath expressions to validators that should be applied to those fields. Custom validators that implement ```ca.uhn.fhir.rest.server.interceptor.validation.fields.IValidator``` interface can be provided.
`FieldValidatingInterceptor` allows validating primitive fields on various FHIR resources. It expects validation rules to be provided via `field-validation-rules.json` file that should be available on the classpath. JSON in this file defines a mapping of FHIRPath expressions to validators that should be applied to those fields. Custom validators that implement `ca.uhn.fhir.rest.server.interceptor.validation.fields.IValidator` interface can be provided.
```json
{
@ -325,4 +343,4 @@ Address validation can be disabled for a given request by providing ```HAPI-Addr
}
```
Field validation can be disabled for a given request by providing ```HAPI-Field-Validation-Disabled: *``` request header. Header value can be any string, it is the presence of the header that disables the validation.
Field validation can be disabled for a given request by providing `HAPI-Field-Validation-Disabled: *` request header. Header value can be any string, it is the presence of the header that disables the validation.

View File

@ -74,10 +74,9 @@ The generator class is version-specific, so you will need to extend the class ap
* DSTU3: [ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider](/hapi-fhir/apidocs/hapi-fhir-structures-dstu2/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.html)
* DSTU3: [org.hl7.fhir.dstu3.hapi.rest.server.ServerCapabilityStatementProvider](/hapi-fhir/apidocs/hapi-fhir-structures-dstu3/org/hl7/fhir/dstu3/hapi/rest/server/ServerCapabilityStatementProvider.html)
* R4: [org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.html)
* R5: [org.hl7.fhir.r5.hapi.rest.server.ServerCapabilityStatementProvider](/hapi-fhir/apidocs/hapi-fhir-structures-r5/org/hl7/fhir/r5/hapi/rest/server/ServerCapabilityStatementProvider.html)
* R4 and later: [ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider](/hapi-fhir/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.html)
In your own class extending this class, you can override the [`getServerConformance()`](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.html#getServerConformance(javax.servlet.http.HttpServletRequest,ca.uhn.fhir.rest.api.server.RequestDetails)) method to provide your own implementation. In this method, call `super.getServerConformance()` to obtain the built-in conformance statement and then add your own information to it.
In your own class extending this class, you can override the `getServerConformance()` method to obtain the built-in conformance statement and then add your own information to it.
# Controlling Response Contents / Encoding / Formatting

View File

@ -33,9 +33,11 @@ import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu2.hapi.rest.server.ServerConformanceProvider;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.slf4j.LoggerFactory;
import ca.uhn.fhir.context.*;
@ -137,8 +139,8 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv
FhirVersionEnum fhirContextVersion = super.getFhirContext().getVersion().getVersion();
switch (fhirContextVersion) {
case R4:
org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider r4ServerCapabilityStatementProvider = new org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider(serverConfiguration);
myR4CapabilityStatement = r4ServerCapabilityStatementProvider.getServerConformance(null, null);
ServerCapabilityStatementProvider r4ServerCapabilityStatementProvider = new ServerCapabilityStatementProvider(getFhirContext(), serverConfiguration);
myR4CapabilityStatement = (CapabilityStatement) r4ServerCapabilityStatementProvider.getServerConformance(null, null);
break;
case DSTU3:
org.hl7.fhir.dstu3.hapi.rest.server.ServerCapabilityStatementProvider dstu3ServerCapabilityStatementProvider = new org.hl7.fhir.dstu3.hapi.rest.server.ServerCapabilityStatementProvider(serverConfiguration);
@ -226,7 +228,7 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv
}
/**
* This method will add a provider to the conformance. This method is almost an exact copy of {@link ca.uhn.fhir.rest.server.RestfulServer#findResourceMethods(Object, Class)} }
* This method will add a provider to the conformance. This method is almost an exact copy of {@link ca.uhn.fhir.rest.server.RestfulServer#findResourceMethods(Object)}
*
* @param theProvider
* an instance of the provider interface

View File

@ -46,7 +46,7 @@ public interface IFhirSystemDao<T, MT> extends IDao {
/**
* Returns a cached count of resources using a cache that regularly
* refreshes in the background. This method will never
* refreshes in the background. This method will never block, and may return null if nothing is in the cache.
*/
@Nullable
Map<String, Long> getResourceCountsFromCache();

View File

@ -50,6 +50,7 @@ import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@ -104,6 +105,13 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
return fetchResource(myStructureDefinitionType, theUrl);
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
IBundleProvider search = myDaoRegistry.getResourceDao("StructureDefinition").search(new SearchParameterMap().setLoadSynchronousUpTo(1000));
return (List<T>) search.getResources(0, 1000);
}
@Override
@SuppressWarnings({"unchecked", "unused"})

View File

@ -30,6 +30,9 @@ import org.springframework.data.repository.query.Param;
public interface INpmPackageVersionResourceDao extends JpaRepository<NpmPackageVersionResourceEntity, Long> {
@Query("SELECT e FROM NpmPackageVersionResourceEntity e WHERE e.myResourceType = :resourceType AND e.myFhirVersion = :fhirVersion AND e.myPackageVersion.myCurrentVersion = true")
Slice<NpmPackageVersionResourceEntity> findCurrentVersionByResourceType(Pageable thePage, @Param("fhirVersion") FhirVersionEnum theFhirVersion, @Param("resourceType") String theResourceType);
@Query("SELECT e FROM NpmPackageVersionResourceEntity e WHERE e.myCanonicalUrl = :url AND e.myFhirVersion = :fhirVersion AND e.myPackageVersion.myCurrentVersion = true")
Slice<NpmPackageVersionResourceEntity> findCurrentVersionByCanonicalUrl(Pageable thePage, @Param("fhirVersion") FhirVersionEnum theFhirVersion, @Param("url") String theCanonicalUrl);

View File

@ -28,6 +28,7 @@ import org.hl7.fhir.utilities.npm.NpmPackage;
import java.io.IOException;
import java.util.Date;
import java.util.List;
public interface IHapiPackageCacheManager extends IPackageCacheManager {
@ -43,6 +44,8 @@ public interface IHapiPackageCacheManager extends IPackageCacheManager {
PackageDeleteOutcomeJson uninstallPackage(String thePackageId, String theVersion);
List<IBaseResource> loadPackageAssetsByType(FhirVersionEnum theFhirVersion, String theResourceType);
class PackageContents {

View File

@ -65,6 +65,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
@ -467,16 +468,20 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
return null;
} else {
NpmPackageVersionResourceEntity contents = slice.getContent().get(0);
ResourcePersistentId binaryPid = new ResourcePersistentId(contents.getResourceBinary().getId());
IBaseBinary binary = getBinaryDao().readByPid(binaryPid);
byte[] resourceContentsBytes = BinaryUtil.getOrCreateData(myCtx, binary).getValue();
String resourceContents = new String(resourceContentsBytes, StandardCharsets.UTF_8);
FhirContext packageContext = getFhirContext(contents.getFhirVersion());
return EncodingEnum.detectEncoding(resourceContents).newParser(packageContext).parseResource(resourceContents);
return loadPackageEntity(contents);
}
}
private IBaseResource loadPackageEntity(NpmPackageVersionResourceEntity contents) {
ResourcePersistentId binaryPid = new ResourcePersistentId(contents.getResourceBinary().getId());
IBaseBinary binary = getBinaryDao().readByPid(binaryPid);
byte[] resourceContentsBytes = BinaryUtil.getOrCreateData(myCtx, binary).getValue();
String resourceContents = new String(resourceContentsBytes, StandardCharsets.UTF_8);
FhirContext packageContext = getFhirContext(contents.getFhirVersion());
return EncodingEnum.detectEncoding(resourceContents).newParser(packageContext).parseResource(resourceContents);
}
@Override
@Transactional
public NpmPackageMetadataJson loadPackageMetadata(String thePackageId) {
@ -641,6 +646,14 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
return retVal;
}
@Override
@Transactional
public List<IBaseResource> loadPackageAssetsByType(FhirVersionEnum theFhirVersion, String theResourceType) {
// List<NpmPackageVersionResourceEntity> outcome = myPackageVersionResourceDao.findAll();
Slice<NpmPackageVersionResourceEntity> outcome = myPackageVersionResourceDao.findCurrentVersionByResourceType(PageRequest.of(0, 1000), theFhirVersion, theResourceType);
return outcome.stream().map(t->loadPackageEntity(t)).collect(Collectors.toList());
}
private void deleteAndExpungeResourceBinary(IIdType theResourceBinaryId, ExpungeOptions theOptions) {
if (myPartitionSettings.isPartitioningEnabled()) {

View File

@ -27,6 +27,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable;
import java.util.List;
public class NpmJpaValidationSupport implements IValidationSupport {
@ -68,4 +69,14 @@ public class NpmJpaValidationSupport implements IValidationSupport {
}
return null;
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
FhirVersionEnum fhirVersion = myFhirContext.getVersion().getVersion();
return (List<T>) myHapiPackageCacheManager.loadPackageAssetsByType(fhirVersion, "StructureDefinition");
}
}

View File

@ -0,0 +1,143 @@
package ca.uhn.fhir.jpa.provider;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ExtensionConstants;
import ca.uhn.fhir.util.ExtensionUtil;
import ca.uhn.fhir.util.FhirTerser;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CapabilityStatement.ConditionalDeleteStatus;
import org.hl7.fhir.r4.model.CapabilityStatement.ResourceVersionPolicy;
import org.hl7.fhir.r4.model.Meta;
import javax.annotation.Nonnull;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* R4+ Only
*/
public class JpaCapabilityStatementProvider extends ServerCapabilityStatementProvider {
private final FhirContext myContext;
private DaoConfig myDaoConfig;
private String myImplementationDescription;
private boolean myIncludeResourceCounts;
private IFhirSystemDao<?, ?> mySystemDao;
/**
* Constructor
*/
public JpaCapabilityStatementProvider(@Nonnull RestfulServer theRestfulServer, @Nonnull IFhirSystemDao<?, ?> theSystemDao, @Nonnull DaoConfig theDaoConfig, @Nonnull ISearchParamRetriever theSearchParamRegistry, IValidationSupport theValidationSupport) {
super(theRestfulServer, theSearchParamRegistry, theValidationSupport);
Validate.notNull(theRestfulServer);
Validate.notNull(theSystemDao);
Validate.notNull(theDaoConfig);
Validate.notNull(theSearchParamRegistry);
myContext = theRestfulServer.getFhirContext();
mySystemDao = theSystemDao;
myDaoConfig = theDaoConfig;
setIncludeResourceCounts(true);
}
@Override
protected void postProcess(FhirTerser theTerser, IBaseConformance theCapabilityStatement) {
super.postProcess(theTerser, theCapabilityStatement);
if (isNotBlank(myImplementationDescription)) {
theTerser.setElement(theCapabilityStatement, "implementation.description", myImplementationDescription);
}
}
@Override
protected void postProcessRest(FhirTerser theTerser, IBase theRest) {
super.postProcessRest(theTerser, theRest);
if (myDaoConfig.getSupportedSubscriptionTypes().contains(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.WEBSOCKET)) {
if (isNotBlank(myDaoConfig.getWebsocketContextPath())) {
ExtensionUtil.setExtension(myContext, theRest, Constants.CAPABILITYSTATEMENT_WEBSOCKET_URL, "uri", myDaoConfig.getWebsocketContextPath());
}
}
}
@Override
protected void postProcessRestResource(FhirTerser theTerser, IBase theResource, String theResourceName) {
super.postProcessRestResource(theTerser, theResource, theResourceName);
theTerser.addElement(theResource, "versioning", ResourceVersionPolicy.VERSIONEDUPDATE.toCode());
if (myDaoConfig.isAllowMultipleDelete()) {
theTerser.addElement(theResource, "conditionalDelete", ConditionalDeleteStatus.MULTIPLE.toCode());
} else {
theTerser.addElement(theResource, "conditionalDelete", ConditionalDeleteStatus.SINGLE.toCode());
}
// Add resource counts
if (myIncludeResourceCounts) {
Map<String, Long> counts = mySystemDao.getResourceCountsFromCache();
if (counts != null) {
Long count = counts.get(theResourceName);
if (count != null) {
ExtensionUtil.setExtension(myContext, theResource, ExtensionConstants.CONF_RESOURCE_COUNT, "decimal", Long.toString(count));
}
}
}
}
public boolean isIncludeResourceCounts() {
return myIncludeResourceCounts;
}
public void setIncludeResourceCounts(boolean theIncludeResourceCounts) {
myIncludeResourceCounts = theIncludeResourceCounts;
}
public void setDaoConfig(DaoConfig myDaoConfig) {
this.myDaoConfig = myDaoConfig;
}
@CoverageIgnore
public void setImplementationDescription(String theImplDesc) {
myImplementationDescription = theImplDesc;
}
@CoverageIgnore
public void setSystemDao(IFhirSystemDao<Bundle, Meta> mySystemDao) {
this.mySystemDao = mySystemDao;
}
}

View File

@ -20,14 +20,13 @@ package ca.uhn.fhir.jpa.provider.dstu3;
* #L%
*/
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ExtensionConstants;
import org.hl7.fhir.dstu3.model.Bundle;
@ -44,7 +43,6 @@ import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.dstu3.model.UriType;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
@ -55,11 +53,12 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
private volatile CapabilityStatement myCachedValue;
private DaoConfig myDaoConfig;
private ISearchParamRegistry mySearchParamRegistry;
private ISearchParamRetriever mySearchParamRegistry;
private String myImplementationDescription;
private boolean myIncludeResourceCounts;
private RestfulServer myRestfulServer;
private IFhirSystemDao<Bundle, Meta> mySystemDao;
/**
* Constructor
*/
@ -73,7 +72,7 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
/**
* Constructor
*/
public JpaConformanceProviderDstu3(RestfulServer theRestfulServer, IFhirSystemDao<Bundle, Meta> theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) {
public JpaConformanceProviderDstu3(RestfulServer theRestfulServer, IFhirSystemDao<Bundle, Meta> theSystemDao, DaoConfig theDaoConfig, ISearchParamRetriever theSearchParamRegistry) {
super(theRestfulServer);
myRestfulServer = theRestfulServer;
mySystemDao = theSystemDao;
@ -83,7 +82,7 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
setIncludeResourceCounts(true);
}
public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
public void setSearchParamRegistry(ISearchParamRetriever theSearchParamRegistry) {
mySearchParamRegistry = theSearchParamRegistry;
}
@ -117,9 +116,8 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
nextResource.getSearchParam().clear();
String resourceName = nextResource.getType();
RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
Collection<RuntimeSearchParam> searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef);
for (RuntimeSearchParam runtimeSp : searchParams) {
Map<String, RuntimeSearchParam> searchParams = mySearchParamRegistry.getActiveSearchParams(resourceName);
for (RuntimeSearchParam runtimeSp : searchParams.values()) {
CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
confSp.setName(runtimeSp.getName());

View File

@ -1,225 +0,0 @@
package ca.uhn.fhir.jpa.provider.r4;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ExtensionConstants;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.r4.model.CapabilityStatement.ConditionalDeleteStatus;
import org.hl7.fhir.r4.model.CapabilityStatement.ResourceVersionPolicy;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.Enumerations.SearchParamType;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.UriType;
import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider {
private volatile CapabilityStatement myCachedValue;
private DaoConfig myDaoConfig;
private ISearchParamRegistry mySearchParamRegistry;
private String myImplementationDescription;
private boolean myIncludeResourceCounts;
private RestfulServer myRestfulServer;
private IFhirSystemDao<Bundle, Meta> mySystemDao;
/**
* Constructor
*/
@CoverageIgnore
public JpaConformanceProviderR4() {
super();
super.setCache(false);
setIncludeResourceCounts(true);
}
/**
* Constructor
*/
public JpaConformanceProviderR4(@Nonnull RestfulServer theRestfulServer, @Nonnull IFhirSystemDao<Bundle, Meta> theSystemDao, @Nonnull DaoConfig theDaoConfig, @Nonnull ISearchParamRegistry theSearchParamRegistry) {
super(theRestfulServer);
Validate.notNull(theRestfulServer);
Validate.notNull(theSystemDao);
Validate.notNull(theDaoConfig);
Validate.notNull(theSearchParamRegistry);
myRestfulServer = theRestfulServer;
mySystemDao = theSystemDao;
myDaoConfig = theDaoConfig;
super.setCache(false);
setIncludeResourceCounts(true);
setSearchParamRegistry(theSearchParamRegistry);
}
public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
mySearchParamRegistry = theSearchParamRegistry;
}
@Override
public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
CapabilityStatement retVal = myCachedValue;
Map<String, Long> counts = null;
if (myIncludeResourceCounts) {
counts = mySystemDao.getResourceCountsFromCache();
}
counts = defaultIfNull(counts, Collections.emptyMap());
retVal = super.getServerConformance(theRequest, theRequestDetails);
for (CapabilityStatementRestComponent nextRest : retVal.getRest()) {
for (CapabilityStatementRestResourceComponent nextResource : nextRest.getResource()) {
nextResource.setVersioning(ResourceVersionPolicy.VERSIONEDUPDATE);
ConditionalDeleteStatus conditionalDelete = nextResource.getConditionalDelete();
if (conditionalDelete == ConditionalDeleteStatus.MULTIPLE && myDaoConfig.isAllowMultipleDelete() == false) {
nextResource.setConditionalDelete(ConditionalDeleteStatus.SINGLE);
}
// Add resource counts
Long count = counts.get(nextResource.getTypeElement().getValueAsString());
if (count != null) {
nextResource.addExtension(new Extension(ExtensionConstants.CONF_RESOURCE_COUNT, new DecimalType(count)));
}
nextResource.getSearchParam().clear();
String resourceName = nextResource.getType();
RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
Collection<RuntimeSearchParam> searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef);
for (RuntimeSearchParam runtimeSp : searchParams) {
CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
confSp.setName(runtimeSp.getName());
confSp.setDocumentation(runtimeSp.getDescription());
confSp.setDefinition(runtimeSp.getUri());
switch (runtimeSp.getParamType()) {
case COMPOSITE:
confSp.setType(SearchParamType.COMPOSITE);
break;
case DATE:
confSp.setType(SearchParamType.DATE);
break;
case NUMBER:
confSp.setType(SearchParamType.NUMBER);
break;
case QUANTITY:
confSp.setType(SearchParamType.QUANTITY);
break;
case REFERENCE:
confSp.setType(SearchParamType.REFERENCE);
break;
case STRING:
confSp.setType(SearchParamType.STRING);
break;
case TOKEN:
confSp.setType(SearchParamType.TOKEN);
break;
case URI:
confSp.setType(SearchParamType.URI);
break;
case SPECIAL:
confSp.setType(SearchParamType.SPECIAL);
break;
case HAS:
// Shouldn't happen
break;
}
}
}
}
if (myDaoConfig.getSupportedSubscriptionTypes().contains(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.WEBSOCKET)) {
if (isNotBlank(myDaoConfig.getWebsocketContextPath())) {
Extension websocketExtension = new Extension();
websocketExtension.setUrl(Constants.CAPABILITYSTATEMENT_WEBSOCKET_URL);
websocketExtension.setValue(new UriType(myDaoConfig.getWebsocketContextPath()));
retVal.getRestFirstRep().addExtension(websocketExtension);
}
}
massage(retVal);
retVal.getImplementation().setDescription(myImplementationDescription);
myCachedValue = retVal;
return retVal;
}
public boolean isIncludeResourceCounts() {
return myIncludeResourceCounts;
}
public void setIncludeResourceCounts(boolean theIncludeResourceCounts) {
myIncludeResourceCounts = theIncludeResourceCounts;
}
/**
* Subclasses may override
*/
protected void massage(CapabilityStatement theStatement) {
// nothing
}
public void setDaoConfig(DaoConfig myDaoConfig) {
this.myDaoConfig = myDaoConfig;
}
@CoverageIgnore
public void setImplementationDescription(String theImplDesc) {
myImplementationDescription = theImplDesc;
}
@Override
public void setRestfulServer(RestfulServer theRestfulServer) {
this.myRestfulServer = theRestfulServer;
super.setRestfulServer(theRestfulServer);
}
@CoverageIgnore
public void setSystemDao(IFhirSystemDao<Bundle, Meta> mySystemDao) {
this.mySystemDao = mySystemDao;
}
}

View File

@ -1,215 +0,0 @@
package ca.uhn.fhir.jpa.provider.r5;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ExtensionConstants;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.ConditionalDeleteStatus;
import org.hl7.fhir.r5.model.CapabilityStatement.ResourceVersionPolicy;
import org.hl7.fhir.r5.model.DecimalType;
import org.hl7.fhir.r5.model.Enumerations.SearchParamType;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.Meta;
import org.hl7.fhir.r5.model.UriType;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class JpaConformanceProviderR5 extends org.hl7.fhir.r5.hapi.rest.server.ServerCapabilityStatementProvider {
private volatile CapabilityStatement myCachedValue;
private DaoConfig myDaoConfig;
private ISearchParamRegistry mySearchParamRegistry;
private String myImplementationDescription;
private boolean myIncludeResourceCounts;
private RestfulServer myRestfulServer;
private IFhirSystemDao<Bundle, Meta> mySystemDao;
/**
* Constructor
*/
@CoverageIgnore
public JpaConformanceProviderR5() {
super();
setIncludeResourceCounts(true);
}
/**
* Constructor
*/
public JpaConformanceProviderR5(RestfulServer theRestfulServer, IFhirSystemDao<Bundle, Meta> theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) {
super();
myRestfulServer = theRestfulServer;
mySystemDao = theSystemDao;
myDaoConfig = theDaoConfig;
setIncludeResourceCounts(true);
setSearchParamRegistry(theSearchParamRegistry);
}
public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
mySearchParamRegistry = theSearchParamRegistry;
}
@Override
public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
CapabilityStatement retVal = myCachedValue;
Map<String, Long> counts = null;
if (myIncludeResourceCounts) {
counts = mySystemDao.getResourceCountsFromCache();
}
counts = defaultIfNull(counts, Collections.emptyMap());
retVal = super.getServerConformance(theRequest, theRequestDetails);
for (CapabilityStatementRestComponent nextRest : retVal.getRest()) {
for (CapabilityStatementRestResourceComponent nextResource : nextRest.getResource()) {
nextResource.setVersioning(ResourceVersionPolicy.VERSIONEDUPDATE);
ConditionalDeleteStatus conditionalDelete = nextResource.getConditionalDelete();
if (conditionalDelete == ConditionalDeleteStatus.MULTIPLE && myDaoConfig.isAllowMultipleDelete() == false) {
nextResource.setConditionalDelete(ConditionalDeleteStatus.SINGLE);
}
// Add resource counts
Long count = counts.get(nextResource.getTypeElement().getValueAsString());
if (count != null) {
nextResource.addExtension(new Extension(ExtensionConstants.CONF_RESOURCE_COUNT, new DecimalType(count)));
}
nextResource.getSearchParam().clear();
String resourceName = nextResource.getType();
RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
Collection<RuntimeSearchParam> searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef);
for (RuntimeSearchParam runtimeSp : searchParams) {
CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
confSp.setName(runtimeSp.getName());
confSp.setDocumentation(runtimeSp.getDescription());
confSp.setDefinition(runtimeSp.getUri());
switch (runtimeSp.getParamType()) {
case COMPOSITE:
confSp.setType(SearchParamType.COMPOSITE);
break;
case DATE:
confSp.setType(SearchParamType.DATE);
break;
case NUMBER:
confSp.setType(SearchParamType.NUMBER);
break;
case QUANTITY:
confSp.setType(SearchParamType.QUANTITY);
break;
case REFERENCE:
confSp.setType(SearchParamType.REFERENCE);
break;
case STRING:
confSp.setType(SearchParamType.STRING);
break;
case TOKEN:
confSp.setType(SearchParamType.TOKEN);
break;
case URI:
confSp.setType(SearchParamType.URI);
break;
case SPECIAL:
confSp.setType(SearchParamType.SPECIAL);
break;
case HAS:
// Shouldn't happen
break;
}
}
}
}
if (myDaoConfig.getSupportedSubscriptionTypes().contains(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.WEBSOCKET)) {
if (isNotBlank(myDaoConfig.getWebsocketContextPath())) {
Extension websocketExtension = new Extension();
websocketExtension.setUrl(Constants.CAPABILITYSTATEMENT_WEBSOCKET_URL);
websocketExtension.setValue(new UriType(myDaoConfig.getWebsocketContextPath()));
retVal.getRestFirstRep().addExtension(websocketExtension);
}
}
massage(retVal);
retVal.getImplementation().setDescription(myImplementationDescription);
myCachedValue = retVal;
return retVal;
}
public boolean isIncludeResourceCounts() {
return myIncludeResourceCounts;
}
public void setIncludeResourceCounts(boolean theIncludeResourceCounts) {
myIncludeResourceCounts = theIncludeResourceCounts;
}
/**
* Subclasses may override
*/
protected void massage(CapabilityStatement theStatement) {
// nothing
}
public void setDaoConfig(DaoConfig myDaoConfig) {
this.myDaoConfig = myDaoConfig;
}
@CoverageIgnore
public void setImplementationDescription(String theImplDesc) {
myImplementationDescription = theImplDesc;
}
@Override
public void setRestfulServer(RestfulServer theRestfulServer) {
this.myRestfulServer = theRestfulServer;
super.setRestfulServer(theRestfulServer);
}
@CoverageIgnore
public void setSystemDao(IFhirSystemDao<Bundle, Meta> mySystemDao) {
this.mySystemDao = mySystemDao;
}
}

View File

@ -64,6 +64,7 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
import ca.uhn.fhir.jpa.provider.r4.BaseJpaResourceProviderObservationR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
@ -93,6 +94,8 @@ import ca.uhn.fhir.rest.server.BasePagingProvider;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.ResourceUtil;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
@ -199,6 +202,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
private static IValidationSupport ourJpaValidationSupportChainR4;
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
@Autowired
protected IPackageInstallerSvc myPackageInstallerSvc;
@Autowired
protected ITermConceptMappingSvc myConceptMappingSvc;
@Autowired
@ -591,13 +596,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
}
protected <T extends IBaseResource> T loadResourceFromClasspath(Class<T> type, String resourceName) throws IOException {
InputStream stream = FhirResourceDaoDstu2SearchNoFtTest.class.getResourceAsStream(resourceName);
if (stream == null) {
fail("Unable to load resource: " + resourceName);
}
String string = IOUtils.toString(stream, "UTF-8");
IParser newJsonParser = EncodingEnum.detectEncodingNoDefault(string).newParser(myFhirCtx);
return newJsonParser.parseResource(type, string);
return ClasspathUtil.loadResource(myFhirCtx, type, resourceName);
}
protected void validate(IBaseResource theResource) {

View File

@ -146,7 +146,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
myObservationDao.validate(obs, null, null, null, null, null, null);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(10, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(12, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());

View File

@ -0,0 +1,173 @@
package ca.uhn.fhir.jpa.interceptor;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.SearchPreferHandlingInterceptor;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class SearchPreferHandlingInterceptorJpaTest extends BaseResourceProviderR4Test {
private SearchPreferHandlingInterceptor mySvc;
@Override
@BeforeEach
public void before() throws Exception {
super.before();
mySvc = new SearchPreferHandlingInterceptor(mySearchParamRegistry);
ourRestServer.registerInterceptor(mySvc);
}
@Override
@AfterEach
public void after() throws Exception {
super.after();
ourRestServer.unregisterInterceptor(mySvc);
}
@Test
public void testSearchWithInvalidParam_NoHeader() {
try {
myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.prettyPrint()
.returnBundle(Bundle.class)
.encodedJson()
.execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]"));
}
}
@Test
public void testSearchWithInvalidParam_StrictHeader() {
try {
myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.withAdditionalHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_HANDLING + "=" + Constants.HEADER_PREFER_HANDLING_STRICT)
.prettyPrint()
.returnBundle(Bundle.class)
.encodedJson()
.execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]"));
}
}
@Test
public void testSearchWithInvalidParam_UnrelatedPreferHeader() {
try {
myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.withAdditionalHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_REPRESENTATION)
.prettyPrint()
.returnBundle(Bundle.class)
.encodedJson()
.execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]"));
}
}
@Test
public void testSearchWithInvalidParam_LenientHeader() {
Bundle outcome = myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.and(Patient.IDENTIFIER.exactly().codes("BLAH"))
.prettyPrint()
.returnBundle(Bundle.class)
.withAdditionalHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_HANDLING + "=" + Constants.HEADER_PREFER_HANDLING_LENIENT)
.encodedJson()
.execute();
assertEquals(0, outcome.getTotal());
assertEquals(ourServerBase + "/Patient?_format=json&_pretty=true&identifier=BLAH", outcome.getLink(Constants.LINK_SELF).getUrl());
}
@Test
public void testSearchWithChain_Invalid() {
try {
myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo.bar").matches().value("bar"))
.withAdditionalHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_HANDLING + "=" + Constants.HEADER_PREFER_HANDLING_STRICT)
.prettyPrint()
.returnBundle(Bundle.class)
.encodedJson()
.execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [_id, _language, active, address, address-city, address-country, address-postalcode, address-state, address-use, birthdate, death-date, deceased, email, family, gender, general-practitioner, given, identifier, language, link, name, organization, phone, phonetic, telecom]"));
}
}
@Test
public void testSearchWithChain_Valid() {
Bundle outcome = myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("organization.name").matches().value("bar"))
.withAdditionalHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_HANDLING + "=" + Constants.HEADER_PREFER_HANDLING_STRICT)
.prettyPrint()
.returnBundle(Bundle.class)
.encodedJson()
.execute();
assertEquals(0, outcome.getTotal());
}
@Test
public void testSearchWithModifier_Valid() {
Bundle outcome = myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("name").matchesExactly().value("bar"))
.withAdditionalHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_HANDLING + "=" + Constants.HEADER_PREFER_HANDLING_STRICT)
.prettyPrint()
.returnBundle(Bundle.class)
.encodedJson()
.execute();
assertEquals(0, outcome.getTotal());
}
}

View File

@ -78,8 +78,6 @@ public class NpmR4Test extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(NpmR4Test.class);
@Autowired
public IPackageInstallerSvc igInstaller;
@Autowired
private IHapiPackageCacheManager myPackageCacheManager;
@Autowired
private NpmJpaValidationSupport myNpmJpaValidationSupport;
@ -140,7 +138,7 @@ public class NpmR4Test extends BaseJpaR4Test {
.setVersion("3.1.0")
.setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)
.setFetchDependencies(true);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
runInTransaction(()->{
SearchParameterMap map = SearchParameterMap.newSynchronous(SearchParameter.SP_BASE, new TokenParam("NamingSystem"));
@ -154,7 +152,7 @@ public class NpmR4Test extends BaseJpaR4Test {
}
});
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
}
@ -164,7 +162,7 @@ public class NpmR4Test extends BaseJpaR4Test {
myFakeNpmServlet.myResponses.put("/nictiz.fhir.nl.stu3.questionnaires/1.0.2", bytes);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("nictiz.fhir.nl.stu3.questionnaires").setVersion("1.0.2").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
// Be sure no further communication with the server
JettyUtil.closeServer(myServer);
@ -203,7 +201,7 @@ public class NpmR4Test extends BaseJpaR4Test {
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.12.0", bytes);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);
assertEquals(1, outcome.getResourcesInstalled().get("CodeSystem"));
// Be sure no further communication with the server
@ -273,7 +271,7 @@ public class NpmR4Test extends BaseJpaR4Test {
resourceList.add("Organization");
PackageInstallationSpec spec = new PackageInstallationSpec().setName("test-organizations").setVersion("1.0.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
spec.setInstallResourceTypes(resourceList);
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);
assertEquals(3, outcome.getResourcesInstalled().get("Organization"));
// Be sure no further communication with the server
@ -310,7 +308,7 @@ public class NpmR4Test extends BaseJpaR4Test {
resourceList.add("Organization");
PackageInstallationSpec spec = new PackageInstallationSpec().setName("test-organizations").setVersion("1.0.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
spec.setInstallResourceTypes(resourceList);
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);
assertEquals(3, outcome.getResourcesInstalled().get("Organization"));
// Be sure no further communication with the server
@ -350,7 +348,7 @@ public class NpmR4Test extends BaseJpaR4Test {
PackageInstallationSpec spec = new PackageInstallationSpec().setName("test-organizations").setVersion("1.0.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
spec.setInstallResourceTypes(resourceList);
try {
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);
fail();
} catch (ImplementationGuideInstallationException theE) {
assertThat(theE.getMessage(), containsString("Resources in a package must have a url or identifier to be loaded by the package installer."));
@ -370,7 +368,7 @@ public class NpmR4Test extends BaseJpaR4Test {
resourceList.add("ImplementationGuide");
PackageInstallationSpec spec = new PackageInstallationSpec().setName("test-ig").setVersion("1.0.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
spec.setInstallResourceTypes(resourceList);
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);
assertEquals(1, outcome.getResourcesInstalled().get("ImplementationGuide"));
// Be sure no further communication with the server
@ -393,7 +391,7 @@ public class NpmR4Test extends BaseJpaR4Test {
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", bytes);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);
assertEquals(0, outcome.getResourcesInstalled().size(), outcome.getResourcesInstalled().toString());
}
@ -408,11 +406,11 @@ public class NpmR4Test extends BaseJpaR4Test {
PackageInstallOutcomeJson outcome;
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
outcome = igInstaller.install(spec);
outcome = myPackageInstallerSvc.install(spec);
assertEquals(1, outcome.getResourcesInstalled().get("CodeSystem"));
igInstaller.install(spec);
outcome = igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
outcome = myPackageInstallerSvc.install(spec);
assertEquals(null, outcome.getResourcesInstalled().get("CodeSystem"));
// Ensure that we loaded the contents
@ -430,7 +428,7 @@ public class NpmR4Test extends BaseJpaR4Test {
myFakeNpmServlet.myResponses.put("/UK.Core.r4/1.1.0", bytes);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("UK.Core.r4").setVersion("1.1.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
// Be sure no further communication with the server
JettyUtil.closeServer(myServer);
@ -450,9 +448,9 @@ public class NpmR4Test extends BaseJpaR4Test {
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz"));
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
runInTransaction(() -> {
NpmPackageMetadataJson metadata = myPackageCacheManager.loadPackageMetadata("hl7.fhir.uv.shorthand");
@ -484,18 +482,18 @@ public class NpmR4Test extends BaseJpaR4Test {
PackageInstallationSpec spec;
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);
ourLog.info("Install messages:\n * {}", outcome.getMessage().stream().collect(Collectors.joining("\n * ")));
assertThat(outcome.getMessage(), hasItem("Marking package hl7.fhir.uv.shorthand#0.12.0 as current version"));
assertThat(outcome.getMessage(), hasItem("Indexing CodeSystem Resource[package/CodeSystem-shorthand-code-system.json] with URL: http://hl7.org/fhir/uv/shorthand/CodeSystem/shorthand-code-system|0.12.0"));
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
outcome = igInstaller.install(spec);
outcome = myPackageInstallerSvc.install(spec);
ourLog.info("Install messages:\n * {}", outcome.getMessage().stream().collect(Collectors.joining("\n * ")));
assertThat(outcome.getMessage(), not(hasItem("Marking package hl7.fhir.uv.shorthand#0.11.1 as current version")));
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
NpmPackage pkg;
@ -519,7 +517,7 @@ public class NpmR4Test extends BaseJpaR4Test {
// Install older version
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
// Older version is current
runInTransaction(() -> {
@ -535,7 +533,7 @@ public class NpmR4Test extends BaseJpaR4Test {
// Now install newer version
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
// Newer version is current
runInTransaction(() -> {
@ -562,7 +560,7 @@ public class NpmR4Test extends BaseJpaR4Test {
// Install newer version
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
runInTransaction(() -> {
@ -578,7 +576,7 @@ public class NpmR4Test extends BaseJpaR4Test {
// Install older version
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
// Newer version is still current
runInTransaction(() -> {
@ -604,7 +602,7 @@ public class NpmR4Test extends BaseJpaR4Test {
// Install
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
runInTransaction(() -> {
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.12.0").orElseThrow(() -> new IllegalArgumentException());
@ -613,7 +611,7 @@ public class NpmR4Test extends BaseJpaR4Test {
// Install same again
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
runInTransaction(() -> {
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.12.0").orElseThrow(() -> new IllegalArgumentException());
@ -634,7 +632,7 @@ public class NpmR4Test extends BaseJpaR4Test {
// Install older version
PackageInstallationSpec spec = new PackageInstallationSpec().setName("test-exchange.fhir.us.com").setVersion("2.1.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
IBundleProvider spSearch = mySearchParameterDao.search(SearchParameterMap.newSynchronous("code", new TokenParam("network-id")));
assertEquals(1, spSearch.sizeOrThrowNpe());
@ -657,7 +655,7 @@ public class NpmR4Test extends BaseJpaR4Test {
// Install newer version
spec = new PackageInstallationSpec().setName("test-exchange.fhir.us.com").setVersion("2.1.2").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
spSearch = mySearchParameterDao.search(SearchParameterMap.newSynchronous("code", new TokenParam("network-id")));
assertEquals(1, spSearch.sizeOrThrowNpe());
@ -675,9 +673,9 @@ public class NpmR4Test extends BaseJpaR4Test {
byte[] contents0120 = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz");
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(contents0111);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(contents0120);
igInstaller.install(spec);
myPackageInstallerSvc.install(spec);
assertArrayEquals(contents0111, myPackageCacheManager.loadPackageContents("hl7.fhir.uv.shorthand", "0.11.1").getBytes());
@ -699,9 +697,9 @@ public class NpmR4Test extends BaseJpaR4Test {
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz"));
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.0", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.0.tgz"));
igInstaller.install(new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
igInstaller.install(new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
igInstaller.install(new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
myPackageInstallerSvc.install(new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
myPackageInstallerSvc.install(new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
myPackageInstallerSvc.install(new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
runInTransaction(() -> {
Slice<NpmPackageVersionResourceEntity> versions = myPackageVersionResourceDao.findCurrentVersionByCanonicalUrl(Pageable.unpaged(), FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ValueSet/shorthand-instance-tags");

View File

@ -7,12 +7,12 @@ import ca.uhn.fhir.jpa.dao.data.IPartitionDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader;
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.StrictErrorHandler;
@ -23,7 +23,6 @@ import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@ -38,7 +37,6 @@ import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
@ -57,8 +55,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.slf4j.LoggerFactory.getLogger;
public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
private static final Logger ourLog = getLogger(BaseResourceProviderR4Test.class);
protected static IValidationSupport myValidationSupport;
protected static CloseableHttpClient ourHttpClient;
@ -155,8 +151,9 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
ourRestServer.registerInterceptor(corsInterceptor);
ourSearchParamRegistry = myAppCtx.getBean(SearchParamRegistryImpl.class);
IValidationSupport validationSupport = myAppCtx.getBean(IValidationSupport.class);
JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(ourRestServer, mySystemDao, myDaoConfig, ourSearchParamRegistry);
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(ourRestServer, mySystemDao, myDaoConfig, ourSearchParamRegistry, validationSupport);
confProvider.setImplementationDescription("THIS IS THE DESC");
ourRestServer.setServerConformanceProvider(confProvider);
@ -169,8 +166,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
myValidationSupport = wac.getBean(IValidationSupport.class);
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
confProvider.setSearchParamRegistry(ourSearchParamRegistry);
myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
myFhirCtx.getRestfulClientFactory().setSocketTimeout(400000);
@ -191,6 +186,13 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
}
}
protected static void clearRestfulServer() throws Exception {
if (ourServer != null) {
JettyUtil.closeServer(ourServer);
}
ourServer = null;
}
protected boolean shouldLogClient() {
return true;
}

View File

@ -0,0 +1,144 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.packages.PackageInstallationSpec;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.SearchParameter;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ServerCapabilityStatementProviderJpaR4Test extends BaseResourceProviderR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(ServerCapabilityStatementProviderJpaR4Test.class);
@Test
public void testCorrectResourcesReflected() {
CapabilityStatement cs = myClient.capabilities().ofType(CapabilityStatement.class).execute();
List<String> resourceTypes = cs.getRest().get(0).getResource().stream().map(t -> t.getType()).collect(Collectors.toList());
assertThat(resourceTypes, hasItems("Patient", "Observation", "SearchParameter"));
}
@Test
public void testCustomSearchParamsReflected() {
SearchParameter fooSp = new SearchParameter();
fooSp.addBase("Patient");
fooSp.setCode("foo");
fooSp.setUrl("http://acme.com/foo");
fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("FOO SP");
fooSp.setDescription("This is a search param!");
fooSp.setExpression("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp);
mySearchParamRegistry.forceRefresh();
CapabilityStatement cs = myClient.capabilities().ofType(CapabilityStatement.class).execute();
List<CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent> fooSearchParams = findSearchParams(cs, "Patient", "foo");
assertEquals(1, fooSearchParams.size());
assertEquals("foo", fooSearchParams.get(0).getName());
assertEquals("http://acme.com/foo", fooSearchParams.get(0).getDefinition());
assertEquals("This is a search param!", fooSearchParams.get(0).getDocumentation());
assertEquals(Enumerations.SearchParamType.TOKEN, fooSearchParams.get(0).getType());
}
@Test
public void testRegisteredProfilesReflected_StoredInServer() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/r4/StructureDefinition-kfdrc-patient.json");
myStructureDefinitionDao.update(sd);
StructureDefinition sd2 = loadResourceFromClasspath(StructureDefinition.class, "/r4/StructureDefinition-kfdrc-patient-no-phi.json");
myStructureDefinitionDao.update(sd2);
CapabilityStatement cs = myClient.capabilities().ofType(CapabilityStatement.class).execute();
List<String> supportedProfiles = findSupportedProfiles(cs, "Patient");
assertThat(supportedProfiles.toString(), supportedProfiles, containsInAnyOrder(
"http://fhir.kids-first.io/StructureDefinition/kfdrc-patient",
"http://fhir.kids-first.io/StructureDefinition/kfdrc-patient-no-phi"
));
}
/**
* Universal profiles like vitalsigns should not be excluded
*/
@Test
public void testRegisteredProfilesReflected_Universal() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/r4/r4-create-structuredefinition-vital-signs.json");
ourLog.info("Stored SD to ID: {}", myStructureDefinitionDao.update(sd).getId());
CapabilityStatement cs = myClient.capabilities().ofType(CapabilityStatement.class).execute();
List<String> supportedProfiles = findSupportedProfiles(cs, "Observation");
assertThat(supportedProfiles.toString(), supportedProfiles, containsInAnyOrder(
"http://hl7.org/fhir/StructureDefinition/vitalsigns"
));
}
@Test
public void testRegisteredProfilesReflected_StoredInPackageRegistry() throws IOException {
byte[] bytes = loadClasspathBytes("/packages/UK.Core.r4-1.1.0.tgz");
PackageInstallationSpec spec = new PackageInstallationSpec()
.setName("UK.Core.r4")
.setVersion("1.1.0")
.setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY)
.setPackageContents(bytes);
myPackageInstallerSvc.install(spec);
CapabilityStatement cs = myClient.capabilities().ofType(CapabilityStatement.class).execute();
List<String> supportedProfiles = findSupportedProfiles(cs, "Patient");
assertThat(supportedProfiles.toString(), supportedProfiles, containsInAnyOrder(
"https://fhir.nhs.uk/R4/StructureDefinition/UKCore-Patient"
));
}
@Nonnull
private List<String> findSupportedProfiles(CapabilityStatement theCapabilityStatement, String theResourceType) {
assertEquals(1, theCapabilityStatement.getRest().size());
return theCapabilityStatement
.getRest()
.get(0)
.getResource()
.stream()
.filter(t -> t.getType().equals(theResourceType))
.findFirst()
.orElseThrow(() -> new IllegalStateException())
.getSupportedProfile()
.stream()
.map(t -> t.getValue())
.collect(Collectors.toList());
}
@Nonnull
private List<CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent> findSearchParams(CapabilityStatement theCapabilityStatement, String theResourceType, String theParamName) {
assertEquals(1, theCapabilityStatement.getRest().size());
return theCapabilityStatement
.getRest()
.get(0)
.getResource()
.stream()
.filter(t -> t.getType().equals(theResourceType))
.findFirst()
.orElseThrow(() -> new IllegalStateException())
.getSearchParam()
.stream()
.filter(t -> t.getName().equals(theParamName))
.collect(Collectors.toList());
}
}

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.dao.r5.BaseJpaR5Test;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
@ -19,10 +20,8 @@ import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@ -110,8 +109,11 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test {
ourRestServer.registerProviders(mySystemProvider, myTerminologyUploaderProvider);
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
IValidationSupport validationSupport = myAppCtx.getBean(IValidationSupport.class);
JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(ourRestServer, mySystemDao, myDaoConfig, ourSearchParamRegistry);
ourSearchParamRegistry = myAppCtx.getBean(SearchParamRegistryImpl.class);
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(ourRestServer, mySystemDao, myDaoConfig, ourSearchParamRegistry, validationSupport);
confProvider.setImplementationDescription("THIS IS THE DESC");
ourRestServer.setServerConformanceProvider(confProvider);
@ -166,11 +168,9 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test {
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext());
myValidationSupport = wac.getBean(IValidationSupport.class);
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
ourSearchParamRegistry = wac.getBean(SearchParamRegistryImpl.class);
ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
confProvider.setSearchParamRegistry(ourSearchParamRegistry);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();

View File

@ -69,7 +69,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
<build>
<pluginManagement>

View File

@ -32,7 +32,7 @@ import ca.uhn.fhir.jpa.mdm.svc.MdmGoldenResourceDeletingSvc;
import ca.uhn.fhir.jpa.mdm.svc.MdmSearchParamSvc;
import ca.uhn.fhir.jpa.mdm.svc.MdmSubmitSvcImpl;
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
import ca.uhn.fhir.mdm.util.MessageHelper;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@ -51,8 +51,8 @@ public class MdmSubmitterConfig {
}
@Bean
MdmRuleValidator mdmRuleValidator(FhirContext theFhirContext) {
return new MdmRuleValidator(theFhirContext, mdmSearchParamSvc());
MdmRuleValidator mdmRuleValidator(FhirContext theFhirContext, ISearchParamRetriever theSearchParamRetriever) {
return new MdmRuleValidator(theFhirContext, theSearchParamRetriever);
}
@Bean

View File

@ -31,7 +31,6 @@ import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
@ -41,7 +40,7 @@ import javax.annotation.Nullable;
import java.util.List;
@Service
public class MdmSearchParamSvc implements ISearchParamRetriever {
public class MdmSearchParamSvc {
@Autowired
FhirContext myFhirContext;
@Autowired
@ -66,18 +65,12 @@ public class MdmSearchParamSvc implements ISearchParamRetriever {
return mySearchParamExtractorService.extractParamValuesAsStrings(activeSearchParam, theResource);
}
@Override
public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
return mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
}
/**
* Given a source type, and a criteria string of the shape name=x&birthDate=y, generate a {@link SearchParameterMap}
* that represents this query.
*
* @param theSourceType the resource type to execute the search on
* @param theCriteria the string search criteria.
*
* @param theCriteria the string search criteria.
* @return the generated SearchParameterMap, or an empty one if there is no criteria.
*/
public SearchParameterMap getSearchParameterMapFromCriteria(String theSourceType, @Nullable String theCriteria) {
@ -91,17 +84,18 @@ public class MdmSearchParamSvc implements ISearchParamRetriever {
}
public ISearchBuilder generateSearchBuilderForType(String theSourceType) {
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theSourceType);
return mySearchBuilderFactory.newSearchBuilder(resourceDao, theSourceType, resourceDao.getResourceType());
}
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theSourceType);
return mySearchBuilderFactory.newSearchBuilder(resourceDao, theSourceType, resourceDao.getResourceType());
}
/**
* Will return true if the types match, or the search param type is '*', otherwise false.
*
* @param theSearchParamType
* @param theResourceType
* @return
*/
public boolean searchParamTypeIsValidForResourceType(String theSearchParamType, String theResourceType) {
return theSearchParamType.equalsIgnoreCase(theResourceType) || theSearchParamType.equalsIgnoreCase("*");
}
public boolean searchParamTypeIsValidForResourceType(String theSearchParamType, String theResourceType) {
return theSearchParamType.equalsIgnoreCase(theResourceType) || theSearchParamType.equalsIgnoreCase("*");
}
}

View File

@ -26,7 +26,12 @@ import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -38,22 +43,12 @@ public class JpaRuntimeSearchParam extends RuntimeSearchParam {
/**
* Constructor
*/
public JpaRuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, boolean theUnique, List<Component> theComponents, Collection<? extends IPrimitiveType<String>> theBase) {
super(theId, theUri, theName, theDescription, thePath, theParamType, createCompositeList(theParamType), theProvidesMembershipInCompartments, theTargets, theStatus, toStrings(theBase));
public JpaRuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, boolean theUnique, List<Component> theComponents, Collection<String> theBase) {
super(theId, theUri, theName, theDescription, thePath, theParamType, createCompositeList(theParamType), theProvidesMembershipInCompartments, theTargets, theStatus, theBase);
myUnique = theUnique;
myComponents = Collections.unmodifiableList(theComponents);
}
private static Collection<String> toStrings(Collection<? extends IPrimitiveType<String>> theBase) {
HashSet<String> retVal = new HashSet<>();
for (IPrimitiveType<String> next : theBase) {
if (isNotBlank(next.getValueAsString())) {
retVal.add(next.getValueAsString());
}
}
return retVal;
}
public List<Component> getComponents() {
return myComponents;
}
@ -62,14 +57,6 @@ public class JpaRuntimeSearchParam extends RuntimeSearchParam {
return myUnique;
}
private static ArrayList<RuntimeSearchParam> createCompositeList(RestSearchParameterTypeEnum theParamType) {
if (theParamType == RestSearchParameterTypeEnum.COMPOSITE) {
return new ArrayList<>();
} else {
return null;
}
}
public static class Component {
private final String myExpression;
private final IBaseReference myReference;
@ -89,5 +76,13 @@ public class JpaRuntimeSearchParam extends RuntimeSearchParam {
}
}
private static ArrayList<RuntimeSearchParam> createCompositeList(RestSearchParameterTypeEnum theParamType) {
if (theParamType == RestSearchParameterTypeEnum.COMPOSITE) {
return new ArrayList<>();
} else {
return null;
}
}
}

View File

@ -25,24 +25,19 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
import ca.uhn.fhir.jpa.cache.ResourceChangeResult;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface ISearchParamRegistry {
public interface ISearchParamRegistry extends ISearchParamRetriever {
/**
* Request that the cache be refreshed now, in the current thread
*/
void forceRefresh();
/**
* @return Returns {@literal null} if no match
*/
RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName);
/**
* @return the number of search parameter entries changed
*/
@ -50,8 +45,6 @@ public interface ISearchParamRegistry {
ReadOnlySearchParamCache getActiveSearchParams();
Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName);
List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames);
List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName);

View File

@ -28,10 +28,13 @@ import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.DatatypeUtil;
import ca.uhn.fhir.util.ExtensionUtil;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.HapiExtensions;
import org.apache.commons.lang3.EnumUtils;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.SearchParameter;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
@ -47,8 +50,10 @@ import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -74,10 +79,8 @@ public class SearchParameterCanonicalizer {
retVal = canonicalizeSearchParameterDstu3((org.hl7.fhir.dstu3.model.SearchParameter) theSearchParameter);
break;
case R4:
retVal = canonicalizeSearchParameterR4((org.hl7.fhir.r4.model.SearchParameter) theSearchParameter);
break;
case R5:
retVal = canonicalizeSearchParameterR5((org.hl7.fhir.r5.model.SearchParameter) theSearchParameter);
retVal = canonicalizeSearchParameterR4Plus(theSearchParameter);
break;
case DSTU2_HL7ORG:
case DSTU2_1:
@ -161,7 +164,7 @@ public class SearchParameterCanonicalizer {
List<JpaRuntimeSearchParam.Component> components = Collections.emptyList();
Collection<? extends IPrimitiveType<String>> base = Collections.singletonList(theNextSp.getBaseElement());
return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, base);
return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, toStrings(base));
}
private JpaRuntimeSearchParam canonicalizeSearchParameterDstu3(org.hl7.fhir.dstu3.model.SearchParameter theNextSp) {
@ -244,66 +247,63 @@ public class SearchParameterCanonicalizer {
components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), next.getDefinition()));
}
return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase());
return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, toStrings(theNextSp.getBase()));
}
private JpaRuntimeSearchParam canonicalizeSearchParameterR4(org.hl7.fhir.r4.model.SearchParameter theNextSp) {
String name = theNextSp.getCode();
String description = theNextSp.getDescription();
String path = theNextSp.getExpression();
private JpaRuntimeSearchParam canonicalizeSearchParameterR4Plus(IBaseResource theNextSp) {
FhirTerser terser = myFhirContext.newTerser();
String name = terser.getSinglePrimitiveValueOrNull(theNextSp, "code");
String description = terser.getSinglePrimitiveValueOrNull(theNextSp, "description");
String path = terser.getSinglePrimitiveValueOrNull(theNextSp, "expression");
List<String> base = terser.getValues(theNextSp, "base", IPrimitiveType.class).stream().map(t -> t.getValueAsString()).collect(Collectors.toList());
RestSearchParameterTypeEnum paramType = null;
RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null;
switch (theNextSp.getType()) {
case COMPOSITE:
switch (terser.getSinglePrimitiveValue(theNextSp, "type").orElse("")) {
case "composite":
paramType = RestSearchParameterTypeEnum.COMPOSITE;
break;
case DATE:
case "date":
paramType = RestSearchParameterTypeEnum.DATE;
break;
case NUMBER:
case "number":
paramType = RestSearchParameterTypeEnum.NUMBER;
break;
case QUANTITY:
case "quantity":
paramType = RestSearchParameterTypeEnum.QUANTITY;
break;
case REFERENCE:
case "reference":
paramType = RestSearchParameterTypeEnum.REFERENCE;
break;
case STRING:
case "string":
paramType = RestSearchParameterTypeEnum.STRING;
break;
case TOKEN:
case "token":
paramType = RestSearchParameterTypeEnum.TOKEN;
break;
case URI:
case "uri":
paramType = RestSearchParameterTypeEnum.URI;
break;
case SPECIAL:
case "special":
paramType = RestSearchParameterTypeEnum.SPECIAL;
break;
case NULL:
}
switch (terser.getSinglePrimitiveValue(theNextSp, "status").orElse("")) {
case "active":
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE;
break;
case "draft":
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT;
break;
case "retired":
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED;
break;
case "unknown":
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN;
break;
}
if (theNextSp.getStatus() != null) {
switch (theNextSp.getStatus()) {
case ACTIVE:
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE;
break;
case DRAFT:
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT;
break;
case RETIRED:
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED;
break;
case UNKNOWN:
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN;
break;
case NULL:
break;
}
}
Set<String> providesMembershipInCompartments = Collections.emptySet();
Set<String> targets = DatatypeUtil.toStringSet(theNextSp.getTarget());
Set<String> targets = terser.getValues(theNextSp, "target", IPrimitiveType.class).stream().map(t -> t.getValueAsString()).collect(Collectors.toSet());
if (isBlank(name) || isBlank(path) || paramType == null) {
if (paramType != RestSearchParameterTypeEnum.COMPOSITE) {
@ -312,111 +312,29 @@ public class SearchParameterCanonicalizer {
}
IIdType id = theNextSp.getIdElement();
String uri = "";
String uri = terser.getSinglePrimitiveValueOrNull(theNextSp, "url");
boolean unique = false;
List<org.hl7.fhir.r4.model.Extension> uniqueExts = theNextSp.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE);
if (uniqueExts.size() > 0) {
IPrimitiveType<?> uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive();
if (uniqueExtsValuePrimitive != null) {
if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) {
unique = true;
}
}
String value = ((IBaseHasExtensions) theNextSp).getExtension()
.stream()
.filter(e -> HapiExtensions.EXT_SP_UNIQUE.equals(e.getUrl()))
.filter(t->t.getValue() instanceof IPrimitiveType)
.map(t->(IPrimitiveType<?>)t.getValue())
.map(t->t.getValueAsString())
.findFirst()
.orElse("");
if ("true".equalsIgnoreCase(value)) {
unique = true;
}
List<JpaRuntimeSearchParam.Component> components = new ArrayList<>();
for (org.hl7.fhir.r4.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) {
components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new Reference(next.getDefinition())));
for (IBase next : terser.getValues(theNextSp, "component")) {
String expression = terser.getSinglePrimitiveValueOrNull(next, "expression");
String definition = terser.getSinglePrimitiveValueOrNull(next, "definition");
components.add(new JpaRuntimeSearchParam.Component(expression, new Reference(definition)));
}
return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase());
}
private JpaRuntimeSearchParam canonicalizeSearchParameterR5(org.hl7.fhir.r5.model.SearchParameter theNextSp) {
String name = theNextSp.getCode();
String description = theNextSp.getDescription();
String path = theNextSp.getExpression();
RestSearchParameterTypeEnum paramType = null;
RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null;
switch (theNextSp.getType()) {
case COMPOSITE:
paramType = RestSearchParameterTypeEnum.COMPOSITE;
break;
case DATE:
paramType = RestSearchParameterTypeEnum.DATE;
break;
case NUMBER:
paramType = RestSearchParameterTypeEnum.NUMBER;
break;
case QUANTITY:
paramType = RestSearchParameterTypeEnum.QUANTITY;
break;
case REFERENCE:
paramType = RestSearchParameterTypeEnum.REFERENCE;
break;
case STRING:
paramType = RestSearchParameterTypeEnum.STRING;
break;
case TOKEN:
paramType = RestSearchParameterTypeEnum.TOKEN;
break;
case URI:
paramType = RestSearchParameterTypeEnum.URI;
break;
case SPECIAL:
paramType = RestSearchParameterTypeEnum.SPECIAL;
break;
case NULL:
break;
}
if (theNextSp.getStatus() != null) {
switch (theNextSp.getStatus()) {
case ACTIVE:
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE;
break;
case DRAFT:
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT;
break;
case RETIRED:
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED;
break;
case UNKNOWN:
status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN;
break;
case NULL:
break;
}
}
Set<String> providesMembershipInCompartments = Collections.emptySet();
Set<String> targets = DatatypeUtil.toStringSet(theNextSp.getTarget());
if (isBlank(name) || isBlank(path) || paramType == null) {
if (paramType != RestSearchParameterTypeEnum.COMPOSITE) {
return null;
}
}
IIdType id = theNextSp.getIdElement();
String uri = "";
boolean unique = false;
List<org.hl7.fhir.r5.model.Extension> uniqueExts = theNextSp.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE);
if (uniqueExts.size() > 0) {
IPrimitiveType<?> uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive();
if (uniqueExtsValuePrimitive != null) {
if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) {
unique = true;
}
}
}
List<JpaRuntimeSearchParam.Component> components = new ArrayList<>();
for (org.hl7.fhir.r5.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) {
components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new org.hl7.fhir.r5.model.Reference(next.getDefinition())));
}
return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase());
return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, base);
}
@ -449,4 +367,16 @@ public class SearchParameterCanonicalizer {
}
}
}
private static Collection<String> toStrings(Collection<? extends IPrimitiveType<String>> theBase) {
HashSet<String> retVal = new HashSet<>();
for (IPrimitiveType<String> next : theBase) {
if (isNotBlank(next.getValueAsString())) {
retVal.add(next.getValueAsString());
}
}
return retVal;
}
}

View File

@ -18,11 +18,6 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1212.jre7</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>

View File

@ -1,6 +1,7 @@
package ca.uhn.fhirtest;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
@ -9,14 +10,13 @@ import ca.uhn.fhir.jpa.bulk.provider.BulkDataExportProvider;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.provider.r5.JpaConformanceProviderR5;
import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
@ -145,7 +145,8 @@ public class TestRestfulServer extends RestfulServer {
providers.add(myAppCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class));
systemDao = myAppCtx.getBean("mySystemDaoR4", IFhirSystemDao.class);
etagSupport = ETagSupportEnum.ENABLED;
JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class));
IValidationSupport validationSupport = myAppCtx.getBean(IValidationSupport.class);
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(this, systemDao, myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class), validationSupport);
confProvider.setImplementationDescription(implDesc);
setServerConformanceProvider(confProvider);
providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class));
@ -164,8 +165,8 @@ public class TestRestfulServer extends RestfulServer {
providers.add(myAppCtx.getBean("mySystemProviderR5", JpaSystemProviderR5.class));
systemDao = myAppCtx.getBean("mySystemDaoR5", IFhirSystemDao.class);
etagSupport = ETagSupportEnum.ENABLED;
JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, systemDao, myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class));
confProvider.setImplementationDescription(implDesc);
IValidationSupport validationSupport = myAppCtx.getBean(IValidationSupport.class);
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(this, systemDao, myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class), validationSupport);
setServerConformanceProvider(confProvider);
providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class));
providers.add(myAppCtx.getBean(GraphQLProvider.class));

View File

@ -1013,11 +1013,14 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* Notify interceptors about the incoming request
* *************************/
HookParams preProcessedParams = new HookParams();
preProcessedParams.add(HttpServletRequest.class, theRequest);
preProcessedParams.add(HttpServletResponse.class, theResponse);
if (!myInterceptorService.callHooks(Pointcut.SERVER_INCOMING_REQUEST_PRE_PROCESSED, preProcessedParams)) {
return;
// Interceptor: SERVER_INCOMING_REQUEST_PRE_PROCESSED
if (myInterceptorService.hasHooks(Pointcut.SERVER_INCOMING_REQUEST_PRE_PROCESSED)) {
HookParams preProcessedParams = new HookParams();
preProcessedParams.add(HttpServletRequest.class, theRequest);
preProcessedParams.add(HttpServletResponse.class, theResponse);
if (!myInterceptorService.callHooks(Pointcut.SERVER_INCOMING_REQUEST_PRE_PROCESSED, preProcessedParams)) {
return;
}
}
String requestPath = getRequestPath(requestFullPath, servletContextPath, servletPath);
@ -1056,6 +1059,18 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
requestDetails.setFhirServerBase(fhirServerBase);
requestDetails.setCompleteUrl(completeUrl);
// Interceptor: SERVER_INCOMING_REQUEST_PRE_HANDLER_SELECTED
if (myInterceptorService.hasHooks(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLER_SELECTED)) {
HookParams preProcessedParams = new HookParams();
preProcessedParams.add(HttpServletRequest.class, theRequest);
preProcessedParams.add(HttpServletResponse.class, theResponse);
preProcessedParams.add(RequestDetails.class, requestDetails);
preProcessedParams.add(ServletRequestDetails.class, requestDetails);
if (!myInterceptorService.callHooks(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLER_SELECTED, preProcessedParams)) {
return;
}
}
validateRequest(requestDetails);
BaseMethodBinding<?> resourceMethod = determineResourceMethod(requestDetails, requestPath);
@ -1063,14 +1078,16 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
RestOperationTypeEnum operation = resourceMethod.getRestOperationType(requestDetails);
requestDetails.setRestOperationType(operation);
// Handle server interceptors
HookParams postProcessedParams = new HookParams();
postProcessedParams.add(RequestDetails.class, requestDetails);
postProcessedParams.add(ServletRequestDetails.class, requestDetails);
postProcessedParams.add(HttpServletRequest.class, theRequest);
postProcessedParams.add(HttpServletResponse.class, theResponse);
if (!myInterceptorService.callHooks(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED, postProcessedParams)) {
return;
// Interceptor: SERVER_INCOMING_REQUEST_POST_PROCESSED
if (myInterceptorService.hasHooks(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)) {
HookParams postProcessedParams = new HookParams();
postProcessedParams.add(RequestDetails.class, requestDetails);
postProcessedParams.add(ServletRequestDetails.class, requestDetails);
postProcessedParams.add(HttpServletRequest.class, theRequest);
postProcessedParams.add(HttpServletResponse.class, theResponse);
if (!myInterceptorService.callHooks(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED, postProcessedParams)) {
return;
}
}
/*

View File

@ -21,28 +21,44 @@ package ca.uhn.fhir.rest.server;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.OperationMethodBinding;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.method.SearchParameter;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import ca.uhn.fhir.util.VersionUtil;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import java.util.stream.Collectors;
import org.hl7.fhir.instance.model.api.IBaseResource;
public class RestfulServerConfiguration {
public class RestfulServerConfiguration implements ISearchParamRetriever {
private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerConfiguration.class);
private Collection<ResourceBinding> resourceBindings;
private List<BaseMethodBinding<?>> serverBindings;
private Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype;
private Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype;
private String implementationDescription;
private String serverVersion = VersionUtil.getVersion();
private String serverName = "HAPI FHIR";
@ -92,15 +108,15 @@ public class RestfulServerConfiguration {
this.serverBindings = theServerBindings;
return this;
}
public Map<String, Class<? extends IBaseResource>> getNameToSharedSupertype() {
return resourceNameToSharedSupertype;
}
public RestfulServerConfiguration setNameToSharedSupertype(Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype) {
this.resourceNameToSharedSupertype = resourceNameToSharedSupertype;
return this;
}
public Map<String, Class<? extends IBaseResource>> getNameToSharedSupertype() {
return resourceNameToSharedSupertype;
}
public RestfulServerConfiguration setNameToSharedSupertype(Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype) {
this.resourceNameToSharedSupertype = resourceNameToSharedSupertype;
return this;
}
/**
* Get the implementationDescription
@ -343,4 +359,94 @@ public class RestfulServerConfiguration {
return retVal.toString();
}
@Override
public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
return getActiveSearchParams(theResourceName).get(theParamName);
}
@Override
public Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
Map<String, RuntimeSearchParam> retVal = new LinkedHashMap<>();
collectMethodBindings()
.getOrDefault(theResourceName, Collections.emptyList())
.stream()
.filter(t -> t.getResourceName().equals(theResourceName))
.filter(t -> t instanceof SearchMethodBinding)
.map(t -> (SearchMethodBinding) t)
.filter(t -> t.getQueryName() == null)
.forEach(t -> createRuntimeBinding(retVal, t));
return retVal;
}
private void createRuntimeBinding(Map<String, RuntimeSearchParam> theMapToPopulate, SearchMethodBinding theSearchMethodBinding) {
List<SearchParameter> parameters = theSearchMethodBinding
.getParameters()
.stream()
.filter(t -> t instanceof SearchParameter)
.map(t -> (SearchParameter) t)
.sorted(SearchParameterComparator.INSTANCE)
.collect(Collectors.toList());
for (SearchParameter nextParameter : parameters) {
String nextParamName = nextParameter.getName();
String nextParamUnchainedName = nextParamName;
if (nextParamName.contains(".")) {
nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.'));
}
String nextParamDescription = nextParameter.getDescription();
/*
* If the parameter has no description, default to the one from the resource
*/
if (StringUtils.isBlank(nextParamDescription)) {
RuntimeResourceDefinition def = getFhirContext().getResourceDefinition(theSearchMethodBinding.getResourceName());
RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName);
if (paramDef != null) {
nextParamDescription = paramDef.getDescription();
}
}
if (theMapToPopulate.containsKey(nextParamUnchainedName)) {
continue;
}
IIdType id = getFhirContext().getVersion().newIdType().setValue("SearchParameter/" + theSearchMethodBinding.getResourceName() + "-" + nextParamName);
String uri = null;
String description = nextParamDescription;
String path = null;
RestSearchParameterTypeEnum type = nextParameter.getParamType();
List<RuntimeSearchParam> compositeOf = Collections.emptyList();
Set<String> providesMembershipInCompartments = Collections.emptySet();
Set<String> targets = Collections.emptySet();
RuntimeSearchParam.RuntimeSearchParamStatusEnum status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE;
Collection<String> base = Collections.singletonList(theSearchMethodBinding.getResourceName());
RuntimeSearchParam param = new RuntimeSearchParam(id, uri, nextParamName, description, path, type, compositeOf, providesMembershipInCompartments, targets, status, base);
theMapToPopulate.put(nextParamName, param);
}
}
private static class SearchParameterComparator implements Comparator<SearchParameter> {
private static final SearchParameterComparator INSTANCE = new SearchParameterComparator();
@Override
public int compare(SearchParameter theO1, SearchParameter theO2) {
if (theO1.isRequired() == theO2.isRequired()) {
return theO1.getName().compareTo(theO2.getName());
}
if (theO1.isRequired()) {
return -1;
}
return 1;
}
}
}

View File

@ -33,6 +33,7 @@ import ca.uhn.fhir.rest.api.BundleLinks;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.PreferHandlingEnum;
import ca.uhn.fhir.rest.api.PreferHeader;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
@ -795,7 +796,7 @@ public class RestfulServerUtils {
PreferHeader retVal = new PreferHeader();
if (isNotBlank(theValue)) {
StringTokenizer tok = new StringTokenizer(theValue, ";");
StringTokenizer tok = new StringTokenizer(theValue, ";,");
while (tok.hasMoreTokens()) {
String next = trim(tok.nextToken());
int eqIndex = next.indexOf('=');
@ -812,15 +813,14 @@ public class RestfulServerUtils {
if (key.equals(Constants.HEADER_PREFER_RETURN)) {
if (value.length() < 2) {
continue;
}
if ('"' == value.charAt(0) && '"' == value.charAt(value.length() - 1)) {
value = value.substring(1, value.length() - 1);
}
value = cleanUpValue(value);
retVal.setReturn(PreferReturnEnum.fromHeaderValue(value));
} else if (key.equals(Constants.HEADER_PREFER_HANDLING)) {
value = cleanUpValue(value);
retVal.setHanding(PreferHandlingEnum.fromHeaderValue(value));
} else if (key.equals(Constants.HEADER_PREFER_RESPOND_ASYNC)) {
retVal.setRespondAsync(true);
@ -836,6 +836,16 @@ public class RestfulServerUtils {
return retVal;
}
private static String cleanUpValue(String value) {
if (value.length() < 2) {
value = "";
}
if ('"' == value.charAt(0) && '"' == value.charAt(value.length() - 1)) {
value = value.substring(1, value.length() - 1);
}
return value;
}
public static boolean prettyPrintResponse(IRestfulServerDefaults theServer, RequestDetails theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();

View File

@ -0,0 +1,166 @@
package ca.uhn.fhir.rest.server.interceptor;
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.PreferHandlingEnum;
import ca.uhn.fhir.rest.api.PreferHeader;
import ca.uhn.fhir.rest.api.server.IRestfulServer;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import org.apache.commons.lang3.Validate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* @since 5.4.0
*/
@Interceptor
public class SearchPreferHandlingInterceptor {
@Nonnull
private PreferHandlingEnum myDefaultBehaviour;
@Nullable
private ISearchParamRetriever mySearchParamRetriever;
/**
* Constructor that uses the {@link RestfulServer} itself to determine
* the allowable search params.
*/
public SearchPreferHandlingInterceptor() {
setDefaultBehaviour(PreferHandlingEnum.STRICT);
}
/**
* Constructor that uses a dedicated {@link ISearchParamRetriever} instance. This is mainly
* intended for the JPA server.
*/
public SearchPreferHandlingInterceptor(ISearchParamRetriever theSearchParamRetriever) {
this();
mySearchParamRetriever = theSearchParamRetriever;
}
@Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLER_SELECTED)
public void incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
if (!SearchMethodBinding.isPlainSearchRequest(theRequestDetails)) {
return;
}
String resourceName = theRequestDetails.getResourceName();
if (!theRequestDetails.getFhirContext().getResourceTypes().contains(resourceName)) {
// This is an error. Let the server handle it normally.
return;
}
String preferHeader = theRequestDetails.getHeader(Constants.HEADER_PREFER);
PreferHandlingEnum handling = null;
if (isNotBlank(preferHeader)) {
PreferHeader parsedPreferHeader = RestfulServerUtils.parsePreferHeader((IRestfulServer<?>) theRequestDetails.getServer(), preferHeader);
handling = parsedPreferHeader.getHanding();
}
// Default behaviour
if (handling == null) {
handling = getDefaultBehaviour();
}
removeUnwantedParams(handling, theRequestDetails);
}
private void removeUnwantedParams(PreferHandlingEnum theHandling, RequestDetails theRequestDetails) {
ISearchParamRetriever searchParamRetriever = mySearchParamRetriever;
if (searchParamRetriever == null) {
searchParamRetriever = ((RestfulServer) theRequestDetails.getServer()).createConfiguration();
}
String resourceName = theRequestDetails.getResourceName();
HashMap<String, String[]> newMap = null;
for (String paramName : theRequestDetails.getParameters().keySet()) {
if (paramName.startsWith("_")) {
continue;
}
// Strip modifiers and chains
for (int i = 0; i < paramName.length(); i++) {
char nextChar = paramName.charAt(i);
if (nextChar == '.' || nextChar == ':') {
paramName = paramName.substring(0, i);
break;
}
}
RuntimeSearchParam activeSearchParam = searchParamRetriever.getActiveSearchParam(resourceName, paramName);
if (activeSearchParam == null) {
if (theHandling == PreferHandlingEnum.LENIENT) {
if (newMap == null) {
newMap = new HashMap<>(theRequestDetails.getParameters());
}
newMap.remove(paramName);
} else {
// Strict handling
List<String> allowedParams = searchParamRetriever.getActiveSearchParams(resourceName).keySet().stream().sorted().distinct().collect(Collectors.toList());
HapiLocalizer localizer = theRequestDetails.getFhirContext().getLocalizer();
String msg = localizer.getMessage("ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter", paramName, resourceName, allowedParams);
throw new InvalidRequestException(msg);
}
}
}
if (newMap != null) {
theRequestDetails.setParameters(newMap);
}
}
public PreferHandlingEnum getDefaultBehaviour() {
return myDefaultBehaviour;
}
public void setDefaultBehaviour(@Nonnull PreferHandlingEnum theDefaultBehaviour) {
Validate.notNull(theDefaultBehaviour, "theDefaultBehaviour must not be null");
myDefaultBehaviour = theDefaultBehaviour;
}
}

View File

@ -55,6 +55,8 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseMethodBinding<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class);
@ -379,7 +381,11 @@ public abstract class BaseMethodBinding<T> {
Class<? extends IBaseResource> returnTypeFromAnnotation = IBaseResource.class;
if (read != null) {
returnTypeFromAnnotation = read.type();
if (isNotBlank(read.typeName())) {
returnTypeFromAnnotation = theContext.getResourceDefinition(read.typeName()).getImplementingClass();
} else {
returnTypeFromAnnotation = read.type();
}
} else if (search != null) {
returnTypeFromAnnotation = search.type();
} else if (history != null) {

View File

@ -148,29 +148,18 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
@Override
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (!mightBeSearchRequest(theRequest)) {
return MethodMatchEnum.NONE;
}
if (theRequest.getId() != null && myIdParamIndex == null) {
ourLog.trace("Method {} doesn't match because ID is not null: {}", getMethod(), theRequest.getId());
return MethodMatchEnum.NONE;
}
if (theRequest.getRequestType() == RequestTypeEnum.GET && theRequest.getOperation() != null && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) {
ourLog.trace("Method {} doesn't match because request type is GET but operation is not null: {}", theRequest.getId(), theRequest.getOperation());
return MethodMatchEnum.NONE;
}
if (theRequest.getRequestType() == RequestTypeEnum.POST && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) {
ourLog.trace("Method {} doesn't match because request type is POST but operation is not _search: {}", theRequest.getId(), theRequest.getOperation());
return MethodMatchEnum.NONE;
}
if (theRequest.getRequestType() != RequestTypeEnum.GET && theRequest.getRequestType() != RequestTypeEnum.POST) {
ourLog.trace("Method {} doesn't match because request type is {}", getMethod(), theRequest.getRequestType());
return MethodMatchEnum.NONE;
}
if (!StringUtils.equals(myCompartmentName, theRequest.getCompartmentName())) {
ourLog.trace("Method {} doesn't match because it is for compartment {} but request is compartment {}", getMethod(), myCompartmentName, theRequest.getCompartmentName());
return MethodMatchEnum.NONE;
}
if (theRequest.getParameters().get(Constants.PARAM_PAGINGACTION) != null) {
return MethodMatchEnum.NONE;
}
if (myQueryName != null) {
String[] queryNameValues = theRequest.getParameters().get(Constants.PARAM_QUERY);
@ -271,6 +260,38 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
return retVal;
}
/**
* Is this request a request for a normal search - Ie. not a named search, nor a compartment
* search, just a plain old search.
*
* @since 5.4.0
*/
public static boolean isPlainSearchRequest(RequestDetails theRequest) {
if (theRequest.getId() != null) {
return false;
}
if (isNotBlank(theRequest.getCompartmentName())) {
return false;
}
return mightBeSearchRequest(theRequest);
}
private static boolean mightBeSearchRequest(RequestDetails theRequest) {
if (theRequest.getRequestType() == RequestTypeEnum.GET && theRequest.getOperation() != null && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) {
return false;
}
if (theRequest.getRequestType() == RequestTypeEnum.POST && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) {
return false;
}
if (theRequest.getRequestType() != RequestTypeEnum.GET && theRequest.getRequestType() != RequestTypeEnum.POST) {
return false;
}
if (theRequest.getParameters().get(Constants.PARAM_PAGINGACTION) != null) {
return false;
}
return true;
}
@Override
public IBundleProvider invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
if (myIdParamIndex != null) {

View File

@ -0,0 +1,613 @@
package ca.uhn.fhir.rest.server.provider;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.Bindings;
import ca.uhn.fhir.rest.server.IServerConformanceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServerConfiguration;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.IParameter;
import ca.uhn.fhir.rest.server.method.OperationMethodBinding;
import ca.uhn.fhir.rest.server.method.OperationMethodBinding.ReturnType;
import ca.uhn.fhir.rest.server.method.OperationParameter;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.method.SearchParameter;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import ca.uhn.fhir.util.FhirTerser;
import com.google.common.collect.TreeMultimap;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* 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%
*/
/**
* Server FHIR Provider which serves the conformance statement for a RESTful server implementation
* <p>
* This class is version independent, but will only work on servers supporting FHIR R4+ (as this was
* the first FHIR release where CapabilityStatement was a normative resource)
*/
public class ServerCapabilityStatementProvider implements IServerConformanceProvider<IBaseConformance> {
private static final Logger ourLog = LoggerFactory.getLogger(ServerCapabilityStatementProvider.class);
private final FhirContext myContext;
private final RestfulServer myServer;
private final ISearchParamRetriever mySearchParamRetriever;
private final RestfulServerConfiguration myServerConfiguration;
private final IValidationSupport myValidationSupport;
private String myPublisher = "Not provided";
/**
* Constructor
*/
public ServerCapabilityStatementProvider(RestfulServer theServer) {
myServer = theServer;
myContext = theServer.getFhirContext();
mySearchParamRetriever = null;
myServerConfiguration = null;
myValidationSupport = null;
}
/**
* Constructor
*/
public ServerCapabilityStatementProvider(FhirContext theContext, RestfulServerConfiguration theServerConfiguration) {
myContext = theContext;
myServerConfiguration = theServerConfiguration;
mySearchParamRetriever = null;
myServer = null;
myValidationSupport = null;
}
/**
* Constructor
*/
public ServerCapabilityStatementProvider(RestfulServer theRestfulServer, ISearchParamRetriever theSearchParamRetriever, IValidationSupport theValidationSupport) {
myContext = theRestfulServer.getFhirContext();
mySearchParamRetriever = theSearchParamRetriever;
myServer = theRestfulServer;
myServerConfiguration = null;
myValidationSupport = theValidationSupport;
}
private void checkBindingForSystemOps(FhirTerser theTerser, IBase theRest, Set<String> theSystemOps, BaseMethodBinding<?> theMethodBinding) {
RestOperationTypeEnum restOperationType = theMethodBinding.getRestOperationType();
if (restOperationType.isSystemLevel()) {
String sysOp = restOperationType.getCode();
if (theSystemOps.contains(sysOp) == false) {
theSystemOps.add(sysOp);
IBase interaction = theTerser.addElement(theRest, "interaction");
theTerser.addElement(interaction, "code", sysOp);
}
}
}
private String conformanceDate(RestfulServerConfiguration theServerConfiguration) {
IPrimitiveType<Date> buildDate = theServerConfiguration.getConformanceDate();
if (buildDate != null && buildDate.getValue() != null) {
try {
return buildDate.getValueAsString();
} catch (DataFormatException e) {
// fall through
}
}
return InstantDt.withCurrentTime().getValueAsString();
}
private RestfulServerConfiguration getServerConfiguration() {
if (myServer != null) {
return myServer.createConfiguration();
}
return myServerConfiguration;
}
/**
* Gets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
* value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
*/
public String getPublisher() {
return myPublisher;
}
/**
* Sets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
* value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
*/
public void setPublisher(String thePublisher) {
myPublisher = thePublisher;
}
@Override
@Metadata
public IBaseConformance getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
HttpServletRequest servletRequest = null;
if (theRequestDetails instanceof ServletRequestDetails) {
servletRequest = ((ServletRequestDetails) theRequestDetails).getServletRequest();
}
RestfulServerConfiguration configuration = getServerConfiguration();
Bindings bindings = configuration.provideBindings();
IBaseConformance retVal = (IBaseConformance) myContext.getResourceDefinition("CapabilityStatement").newInstance();
FhirTerser terser = myContext.newTerser();
TreeMultimap<String, String> resourceTypeToSupportedProfiles = getSupportedProfileMultimap(terser);
terser.addElement(retVal, "name", "RestServer");
terser.addElement(retVal, "publisher", myPublisher);
terser.addElement(retVal, "date", conformanceDate(configuration));
terser.addElement(retVal, "fhirVersion", myContext.getVersion().getVersion().getFhirVersionString());
ServletContext servletContext = (ServletContext) (theRequest == null ? null : theRequest.getAttribute(RestfulServer.SERVLET_CONTEXT_ATTRIBUTE));
String serverBase = configuration.getServerAddressStrategy().determineServerBase(servletContext, theRequest);
terser.addElement(retVal, "implementation.url", serverBase);
terser.addElement(retVal, "implementation.description", configuration.getImplementationDescription());
terser.addElement(retVal, "kind", "instance");
terser.addElement(retVal, "software.name", configuration.getServerName());
terser.addElement(retVal, "software.version", configuration.getServerVersion());
terser.addElement(retVal, "format", Constants.CT_FHIR_XML_NEW);
terser.addElement(retVal, "format", Constants.CT_FHIR_JSON_NEW);
terser.addElement(retVal, "format", Constants.FORMAT_JSON);
terser.addElement(retVal, "format", Constants.FORMAT_XML);
terser.addElement(retVal, "status", "active");
IBase rest = terser.addElement(retVal, "rest");
terser.addElement(rest, "mode", "server");
Set<String> systemOps = new HashSet<>();
Set<String> operationNames = new HashSet<>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = configuration.collectMethodBindings();
Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype = configuration.getNameToSharedSupertype();
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
if (nextEntry.getKey().isEmpty() == false) {
Set<String> resourceOps = new HashSet<>();
Set<String> resourceIncludes = new HashSet<>();
IBase resource = terser.addElement(rest, "resource");
String resourceName = nextEntry.getKey();
postProcessRestResource(terser, resource, resourceName);
RuntimeResourceDefinition def;
FhirContext context = configuration.getFhirContext();
if (resourceNameToSharedSupertype.containsKey(resourceName)) {
def = context.getResourceDefinition(resourceNameToSharedSupertype.get(resourceName));
} else {
def = context.getResourceDefinition(resourceName);
}
terser.addElement(resource, "type", def.getName());
terser.addElement(resource, "profile", def.getResourceProfile(serverBase));
for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
RestOperationTypeEnum resOpCode = nextMethodBinding.getRestOperationType();
if (resOpCode.isTypeLevel() || resOpCode.isInstanceLevel()) {
String resOp;
resOp = resOpCode.getCode();
if (resourceOps.contains(resOp) == false) {
resourceOps.add(resOp);
IBase interaction = terser.addElement(resource, "interaction");
terser.addElement(interaction, "code", resOp);
}
if (RestOperationTypeEnum.VREAD.equals(resOpCode)) {
// vread implies read
resOp = "read";
if (resourceOps.contains(resOp) == false) {
resourceOps.add(resOp);
IBase interaction = terser.addElement(resource, "interaction");
terser.addElement(interaction, "code", resOp);
}
}
}
if (nextMethodBinding.isSupportsConditional()) {
switch (resOpCode) {
case CREATE:
terser.setElement(resource, "conditionalCreate", "true");
break;
case DELETE:
if (nextMethodBinding.isSupportsConditionalMultiple()) {
terser.setElement(resource, "conditionalDelete", "multiple");
} else {
terser.setElement(resource, "conditionalDelete", "single");
}
break;
case UPDATE:
terser.setElement(resource, "conditionalUpdate", "true");
break;
default:
break;
}
}
checkBindingForSystemOps(terser, rest, systemOps, nextMethodBinding);
if (nextMethodBinding instanceof SearchMethodBinding) {
SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
if (methodBinding.getQueryName() != null) {
String queryName = bindings.getNamedSearchMethodBindingToName().get(methodBinding);
if (operationNames.add(queryName)) {
IBase operation = terser.addElement(rest, "operation");
terser.addElement(operation, "name", methodBinding.getQueryName());
terser.addElement(operation, "definition", (getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + queryName));
}
} else {
resourceIncludes.addAll(methodBinding.getIncludes());
}
} else if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
String opName = bindings.getOperationBindingToName().get(methodBinding);
// Only add each operation (by name) once
if (operationNames.add(opName)) {
IBase operation = terser.addElement(rest, "operation");
terser.addElement(operation, "name", methodBinding.getName().substring(1));
terser.addElement(operation, "definition", getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + opName);
}
}
}
ISearchParamRetriever searchParamRetriever = mySearchParamRetriever;
if (searchParamRetriever == null && myServerConfiguration != null) {
searchParamRetriever = myServerConfiguration;
} else if (searchParamRetriever == null) {
searchParamRetriever = myServer.createConfiguration();
}
Map<String, RuntimeSearchParam> searchParams = searchParamRetriever.getActiveSearchParams(resourceName);
if (searchParams != null) {
for (RuntimeSearchParam next : searchParams.values()) {
IBase searchParam = terser.addElement(resource, "searchParam");
terser.addElement(searchParam, "name", next.getName());
terser.addElement(searchParam, "type", next.getParamType().getCode());
if (isNotBlank(next.getDescription())) {
terser.addElement(searchParam, "documentation", next.getDescription());
}
String spUri = next.getUri();
if (isBlank(spUri) && servletRequest != null) {
String id;
if (next.getId() != null) {
id = next.getId().toUnqualifiedVersionless().getValue();
} else {
id = resourceName + "-" + next.getName();
}
spUri = configuration.getServerAddressStrategy().determineServerBase(servletRequest.getServletContext(), servletRequest) + "/" + id;
}
if (isNotBlank(spUri)) {
terser.addElement(searchParam, "definition", spUri);
}
}
if (resourceIncludes.isEmpty()) {
for (String nextInclude : searchParams.values().stream().filter(t -> t.getParamType() == RestSearchParameterTypeEnum.REFERENCE).map(t -> t.getName()).sorted().collect(Collectors.toList())) {
terser.addElement(resource, "searchInclude", nextInclude);
}
}
}
for (String supportedProfile : resourceTypeToSupportedProfiles.get(resourceName)) {
terser.addElement(resource, "supportedProfile", supportedProfile);
}
for (String resourceInclude : resourceIncludes) {
terser.addElement(resource, "searchInclude", resourceInclude);
}
} else {
for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
checkBindingForSystemOps(terser, rest, systemOps, nextMethodBinding);
if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
String opName = bindings.getOperationBindingToName().get(methodBinding);
if (operationNames.add(opName)) {
ourLog.debug("Found bound operation: {}", opName);
IBase operation = terser.addElement(rest, "operation");
terser.addElement(operation, "name", methodBinding.getName().substring(1));
terser.addElement(operation, "definition", getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + opName);
}
}
}
}
postProcessRest(terser, rest);
}
postProcess(terser, retVal);
return retVal;
}
private TreeMultimap<String, String> getSupportedProfileMultimap(FhirTerser terser) {
TreeMultimap<String, String> resourceTypeToSupportedProfiles = TreeMultimap.create();
if (myValidationSupport != null) {
List<IBaseResource> allStructureDefinitions = myValidationSupport.fetchAllNonBaseStructureDefinitions();
if (allStructureDefinitions != null) {
for (IBaseResource next : allStructureDefinitions) {
String id = next.getIdElement().getValue();
String kind = terser.getSinglePrimitiveValueOrNull(next, "kind");
String url = terser.getSinglePrimitiveValueOrNull(next, "url");
String baseDefinition = defaultString(terser.getSinglePrimitiveValueOrNull(next, "baseDefinition"));
if ("resource".equals(kind) && isNotBlank(url)) {
// Don't include the base resource definitions in the supported profile list - This isn't helpful
if (baseDefinition.equals("http://hl7.org/fhir/StructureDefinition/DomainResource") || baseDefinition.equals("http://hl7.org/fhir/StructureDefinition/Resource")) {
continue;
}
String resourceType = terser.getSinglePrimitiveValueOrNull(next, "snapshot.element.path");
if (isBlank(resourceType)) {
resourceType = terser.getSinglePrimitiveValueOrNull(next, "differential.element.path");
}
if (isNotBlank(resourceType)) {
resourceTypeToSupportedProfiles.put(resourceType, url);
}
}
}
}
}
return resourceTypeToSupportedProfiles;
}
/**
* Subclasses may override
*/
protected void postProcess(FhirTerser theTerser, IBaseConformance theCapabilityStatement) {
// nothing
}
/**
* Subclasses may override
*/
protected void postProcessRest(FhirTerser theTerser, IBase theRest) {
// nothing
}
/**
* Subclasses may override
*/
protected void postProcessRestResource(FhirTerser theTerser, IBase theResource, String theResourceName) {
// nothing
}
protected String getOperationDefinitionPrefix(RequestDetails theRequestDetails) {
if (theRequestDetails == null) {
return "";
}
return theRequestDetails.getServerBaseForRequest() + "/";
}
@Read(typeName = "OperationDefinition")
public IBaseResource readOperationDefinition(@IdParam IIdType theId, RequestDetails theRequestDetails) {
if (theId == null || theId.hasIdPart() == false) {
throw new ResourceNotFoundException(theId);
}
RestfulServerConfiguration configuration = getServerConfiguration();
Bindings bindings = configuration.provideBindings();
List<OperationMethodBinding> operationBindings = bindings.getOperationNameToBindings().get(theId.getIdPart());
if (operationBindings != null && !operationBindings.isEmpty()) {
return readOperationDefinitionForOperation(operationBindings);
}
List<SearchMethodBinding> searchBindings = bindings.getSearchNameToBindings().get(theId.getIdPart());
if (searchBindings != null && !searchBindings.isEmpty()) {
return readOperationDefinitionForNamedSearch(searchBindings);
}
throw new ResourceNotFoundException(theId);
}
private IBaseResource readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) {
IBaseResource op = myContext.getResourceDefinition("OperationDefinition").newInstance();
FhirTerser terser = myContext.newTerser();
terser.addElement(op, "status", "active");
terser.addElement(op, "kind", "query");
terser.addElement(op, "affectsState", "false");
terser.addElement(op, "instance", "false");
Set<String> inParams = new HashSet<>();
String operationCode = null;
for (SearchMethodBinding binding : bindings) {
if (isNotBlank(binding.getDescription())) {
terser.addElement(op, "description", binding.getDescription());
}
if (isBlank(binding.getResourceProviderResourceName())) {
terser.addElement(op, "system", "true");
terser.addElement(op, "type", "false");
} else {
terser.addElement(op, "system", "false");
terser.addElement(op, "type", "true");
terser.addElement(op, "resource", binding.getResourceProviderResourceName());
}
if (operationCode == null) {
operationCode = binding.getQueryName();
}
for (IParameter nextParamUntyped : binding.getParameters()) {
if (nextParamUntyped instanceof SearchParameter) {
SearchParameter nextParam = (SearchParameter) nextParamUntyped;
if (!inParams.add(nextParam.getName())) {
continue;
}
IBase param = terser.addElement(op, "parameter");
terser.addElement(param, "use", "in");
terser.addElement(param, "type", "string");
terser.addElement(param, "searchType", nextParam.getParamType().getCode());
terser.addElement(param, "min", nextParam.isRequired() ? "1" : "0");
terser.addElement(param, "max", "1");
terser.addElement(param, "name", nextParam.getName());
}
}
}
terser.addElement(op, "code", operationCode);
terser.addElement(op, "name", "Search_" + operationCode);
return op;
}
private IBaseResource readOperationDefinitionForOperation(List<OperationMethodBinding> bindings) {
IBaseResource op = myContext.getResourceDefinition("OperationDefinition").newInstance();
FhirTerser terser = myContext.newTerser();
terser.addElement(op, "status", "active");
terser.addElement(op, "kind", "operation");
boolean systemLevel = false;
boolean typeLevel = false;
boolean instanceLevel = false;
boolean affectsState = false;
String description = null;
String code = null;
String name;
Set<String> resourceNames = new TreeSet<>();
Set<String> inParams = new HashSet<>();
Set<String> outParams = new HashSet<>();
for (OperationMethodBinding sharedDescription : bindings) {
if (isNotBlank(sharedDescription.getDescription()) && isBlank(description)) {
description = sharedDescription.getDescription();
}
if (sharedDescription.isCanOperateAtInstanceLevel()) {
instanceLevel = true;
}
if (sharedDescription.isCanOperateAtServerLevel()) {
systemLevel = true;
}
if (sharedDescription.isCanOperateAtTypeLevel()) {
typeLevel = true;
}
if (!sharedDescription.isIdempotent()) {
affectsState |= true;
}
code = sharedDescription.getName().substring(1);
if (isNotBlank(sharedDescription.getResourceName())) {
resourceNames.add(sharedDescription.getResourceName());
}
for (IParameter nextParamUntyped : sharedDescription.getParameters()) {
if (nextParamUntyped instanceof OperationParameter) {
OperationParameter nextParam = (OperationParameter) nextParamUntyped;
if (!inParams.add(nextParam.getName())) {
continue;
}
IBase param = terser.addElement(op, "parameter");
terser.addElement(param, "use", "in");
if (nextParam.getParamType() != null) {
terser.addElement(param, "type", nextParam.getParamType());
}
if (nextParam.getSearchParamType() != null) {
terser.addElement(param, "searchType", nextParam.getSearchParamType());
}
terser.addElement(param, "min", Integer.toString(nextParam.getMin()));
terser.addElement(param, "max", (nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())));
terser.addElement(param, "name", nextParam.getName());
}
}
for (ReturnType nextParam : sharedDescription.getReturnParams()) {
if (!outParams.add(nextParam.getName())) {
continue;
}
IBase param = terser.addElement(op, "parameter");
terser.addElement(param, "use", "out");
if (nextParam.getType() != null) {
terser.addElement(param, "type", nextParam.getType());
}
terser.addElement(param, "min", Integer.toString(nextParam.getMin()));
terser.addElement(param, "max", (nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())));
terser.addElement(param, "name", nextParam.getName());
}
}
name = "Operation_" + code;
terser.addElements(op, "resource", resourceNames);
terser.addElement(op, "name", name);
terser.addElement(op, "code", code);
terser.addElement(op, "description", description);
terser.addElement(op, "affectsState", Boolean.toString(affectsState));
terser.addElement(op, "system", Boolean.toString(systemLevel));
terser.addElement(op, "type", Boolean.toString(typeLevel));
terser.addElement(op, "instance", Boolean.toString(instanceLevel));
return op;
}
@Override
public void setRestfulServer(RestfulServer theRestfulServer) {
// ignore
}
}

View File

@ -26,6 +26,8 @@ import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServerConfiguration;
import org.apache.commons.lang3.Validate;
import javax.annotation.Nullable;
public class BaseServerCapabilityStatementProvider {
private RestfulServerConfiguration myConfiguration;
@ -39,7 +41,7 @@ public class BaseServerCapabilityStatementProvider {
}
protected RestfulServerConfiguration getServerConfiguration(RequestDetails theRequestDetails) {
protected RestfulServerConfiguration getServerConfiguration(@Nullable RequestDetails theRequestDetails) {
RestfulServerConfiguration retVal;
if (theRequestDetails != null && theRequestDetails.getServer() instanceof RestfulServer) {
retVal = ((RestfulServer) theRequestDetails.getServer()).createConfiguration();

View File

@ -22,9 +22,17 @@ package ca.uhn.fhir.rest.server.util;
import ca.uhn.fhir.context.RuntimeSearchParam;
import java.util.Map;
public interface ISearchParamRetriever {
/**
* @return Returns {@literal null} if no match
*/
RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName);
/**
* @return Returns all active search params for the given resource
*/
Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName);
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.rest.api.PreferHandlingEnum;
import ca.uhn.fhir.rest.api.PreferHeader;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import org.junit.jupiter.api.Test;
@ -28,4 +29,28 @@ public class RestfulServerUtilsTest{
assertEquals(null, header.getReturn());
assertTrue(header.getRespondAsync());
}
@Test
public void testParseHandlingLenient() {
PreferHeader header = RestfulServerUtils.parsePreferHeader(null,"handling=lenient");
assertEquals(null, header.getReturn());
assertFalse(header.getRespondAsync());
assertEquals(PreferHandlingEnum.LENIENT, header.getHanding());
}
@Test
public void testParseHandlingLenientAndReturnRepresentation_CommaSeparatd() {
PreferHeader header = RestfulServerUtils.parsePreferHeader(null,"handling=lenient, return=representation");
assertEquals(PreferReturnEnum.REPRESENTATION, header.getReturn());
assertFalse(header.getRespondAsync());
assertEquals(PreferHandlingEnum.LENIENT, header.getHanding());
}
@Test
public void testParseHandlingLenientAndReturnRepresentation_SemicolonSeparatd() {
PreferHeader header = RestfulServerUtils.parsePreferHeader(null,"handling=lenient; return=representation");
assertEquals(PreferReturnEnum.REPRESENTATION, header.getReturn());
assertFalse(header.getRespondAsync());
assertEquals(PreferHandlingEnum.LENIENT, header.getHanding());
}
}

View File

@ -2,9 +2,10 @@ package org.hl7.fhir.r4.hapi.ctx;
import ca.uhn.fhir.rest.api.server.IFhirVersionServer;
import ca.uhn.fhir.rest.server.RestfulServer;
import org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider;
import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
public class FhirServerR4 implements IFhirVersionServer {
@Override
public ServerCapabilityStatementProvider createServerConformanceProvider(RestfulServer theServer) {
return new ServerCapabilityStatementProvider(theServer);

View File

@ -1,578 +0,0 @@
package org.hl7.fhir.r4.hapi.rest.server;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.Bindings;
import ca.uhn.fhir.rest.server.IServerConformanceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServerConfiguration;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.method.*;
import ca.uhn.fhir.rest.server.method.SearchParameter;
import ca.uhn.fhir.rest.server.method.OperationMethodBinding.ReturnType;
import ca.uhn.fhir.rest.server.util.BaseServerCapabilityStatementProvider;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.CapabilityStatement.*;
import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r4.model.OperationDefinition.OperationDefinitionParameterComponent;
import org.hl7.fhir.r4.model.OperationDefinition.OperationKind;
import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.Map.Entry;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import ca.uhn.fhir.context.FhirContext;
/*
* #%L
* HAPI FHIR Structures - DSTU2 (FHIR v1.0.0)
* %%
* Copyright (C) 2014 - 2015 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%
*/
/**
* Server FHIR Provider which serves the conformance statement for a RESTful server implementation
*
* <p>
* Note: This class is safe to extend, but it is important to note that the same instance of {@link CapabilityStatement} is always returned unless {@link #setCache(boolean)} is called with a value of
* <code>false</code>. This means that if you are adding anything to the returned conformance instance on each call you should call <code>setCache(false)</code> in your provider constructor.
* </p>
*/
public class ServerCapabilityStatementProvider extends BaseServerCapabilityStatementProvider implements IServerConformanceProvider<CapabilityStatement> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProvider.class);
private String myPublisher = "Not provided";
/**
* No-arg constructor and setter so that the ServerConformanceProvider can be Spring-wired with the RestfulService avoiding the potential reference cycle that would happen.
*/
public ServerCapabilityStatementProvider() {
super();
}
/**
* Constructor
*
* @deprecated Use no-args constructor instead. Deprecated in 4.0.0
*/
@Deprecated
public ServerCapabilityStatementProvider(RestfulServer theRestfulServer) {
this();
}
/**
* Constructor - This is intended only for JAX-RS server
*/
public ServerCapabilityStatementProvider(RestfulServerConfiguration theServerConfiguration) {
super(theServerConfiguration);
}
private void checkBindingForSystemOps(CapabilityStatementRestComponent rest, Set<SystemRestfulInteraction> systemOps, BaseMethodBinding<?> nextMethodBinding) {
if (nextMethodBinding.getRestOperationType() != null) {
String sysOpCode = nextMethodBinding.getRestOperationType().getCode();
if (sysOpCode != null) {
SystemRestfulInteraction sysOp;
try {
sysOp = SystemRestfulInteraction.fromCode(sysOpCode);
} catch (FHIRException e) {
return;
}
if (sysOp == null) {
return;
}
if (systemOps.contains(sysOp) == false) {
systemOps.add(sysOp);
rest.addInteraction().setCode(sysOp);
}
}
}
}
private DateTimeType conformanceDate(RequestDetails theRequestDetails) {
IPrimitiveType<Date> buildDate = getServerConfiguration(theRequestDetails).getConformanceDate();
if (buildDate != null && buildDate.getValue() != null) {
try {
return new DateTimeType(buildDate.getValueAsString());
} catch (DataFormatException e) {
// fall through
}
}
return DateTimeType.now();
}
/**
* Gets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
* value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
*/
public String getPublisher() {
return myPublisher;
}
/**
* Sets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
* value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
*/
public void setPublisher(String thePublisher) {
myPublisher = thePublisher;
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
@Override
@Metadata
public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
RestfulServerConfiguration configuration = getServerConfiguration(theRequestDetails);
Bindings bindings = configuration.provideBindings();
CapabilityStatement retVal = new CapabilityStatement();
retVal.setPublisher(myPublisher);
retVal.setDateElement(conformanceDate(theRequestDetails));
retVal.setFhirVersion(Enumerations.FHIRVersion.fromCode(FhirVersionEnum.R4.getFhirVersionString()));
ServletContext servletContext = (ServletContext) (theRequest == null ? null : theRequest.getAttribute(RestfulServer.SERVLET_CONTEXT_ATTRIBUTE));
String serverBase = configuration.getServerAddressStrategy().determineServerBase(servletContext, theRequest);
retVal
.getImplementation()
.setUrl(serverBase)
.setDescription(configuration.getImplementationDescription());
retVal.setKind(CapabilityStatementKind.INSTANCE);
retVal.getSoftware().setName(configuration.getServerName());
retVal.getSoftware().setVersion(configuration.getServerVersion());
retVal.addFormat(Constants.CT_FHIR_XML_NEW);
retVal.addFormat(Constants.CT_FHIR_JSON_NEW);
retVal.addFormat(Constants.FORMAT_JSON);
retVal.addFormat(Constants.FORMAT_XML);
retVal.setStatus(PublicationStatus.ACTIVE);
CapabilityStatementRestComponent rest = retVal.addRest();
rest.setMode(RestfulCapabilityMode.SERVER);
Set<SystemRestfulInteraction> systemOps = new HashSet<>();
Set<String> operationNames = new HashSet<>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = configuration.collectMethodBindings();
Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype = configuration.getNameToSharedSupertype();
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
if (nextEntry.getKey().isEmpty() == false) {
Set<TypeRestfulInteraction> resourceOps = new HashSet<>();
CapabilityStatementRestResourceComponent resource = rest.addResource();
String resourceName = nextEntry.getKey();
RuntimeResourceDefinition def;
FhirContext context = configuration.getFhirContext();
if (resourceNameToSharedSupertype.containsKey(resourceName)) {
def = context.getResourceDefinition(resourceNameToSharedSupertype.get(resourceName));
} else {
def = context.getResourceDefinition(resourceName);
}
resource.getTypeElement().setValue(def.getName());
resource.getProfileElement().setValue((def.getResourceProfile(serverBase)));
TreeSet<String> includes = new TreeSet<>();
// Map<String, CapabilityStatement.RestResourceSearchParam> nameToSearchParam = new HashMap<String,
// CapabilityStatement.RestResourceSearchParam>();
for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
nextMethodBinding.getRestOperationType();
String resOpCode = nextMethodBinding.getRestOperationType().getCode();
if (resOpCode != null) {
TypeRestfulInteraction resOp;
try {
resOp = TypeRestfulInteraction.fromCode(resOpCode);
} catch (Exception e) {
resOp = null;
}
if (resOp != null) {
if (resourceOps.contains(resOp) == false) {
resourceOps.add(resOp);
resource.addInteraction().setCode(resOp);
}
if ("vread".equals(resOpCode)) {
// vread implies read
resOp = TypeRestfulInteraction.READ;
if (resourceOps.contains(resOp) == false) {
resourceOps.add(resOp);
resource.addInteraction().setCode(resOp);
}
}
if (nextMethodBinding.isSupportsConditional()) {
switch (resOp) {
case CREATE:
resource.setConditionalCreate(true);
break;
case DELETE:
if (nextMethodBinding.isSupportsConditionalMultiple()) {
resource.setConditionalDelete(ConditionalDeleteStatus.MULTIPLE);
} else {
resource.setConditionalDelete(ConditionalDeleteStatus.SINGLE);
}
break;
case UPDATE:
resource.setConditionalUpdate(true);
break;
default:
break;
}
}
}
}
checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
if (nextMethodBinding instanceof SearchMethodBinding) {
SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
if (methodBinding.getQueryName() != null) {
String queryName = bindings.getNamedSearchMethodBindingToName().get(methodBinding);
if (operationNames.add(queryName)) {
rest.addOperation().setName(methodBinding.getQueryName()).setDefinition((getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + queryName));
}
} else {
handleNamelessSearchMethodBinding(resource, def, includes, (SearchMethodBinding) nextMethodBinding, theRequestDetails);
}
} else if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
String opName = bindings.getOperationBindingToName().get(methodBinding);
if (operationNames.add(opName)) {
// Only add each operation (by name) once
rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition((getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + opName));
}
}
resource.getInteraction().sort(new Comparator<ResourceInteractionComponent>() {
@Override
public int compare(ResourceInteractionComponent theO1, ResourceInteractionComponent theO2) {
TypeRestfulInteraction o1 = theO1.getCode();
TypeRestfulInteraction o2 = theO2.getCode();
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return 1;
}
if (o2 == null) {
return -1;
}
return o1.ordinal() - o2.ordinal();
}
});
}
for (String nextInclude : includes) {
resource.addSearchInclude(nextInclude);
}
} else {
for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
String opName = bindings.getOperationBindingToName().get(methodBinding);
if (operationNames.add(opName)) {
ourLog.debug("Found bound operation: {}", opName);
rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition((getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + opName));
}
}
}
}
}
return retVal;
}
protected String getOperationDefinitionPrefix(RequestDetails theRequestDetails) {
if (theRequestDetails == null) {
return "";
}
return theRequestDetails.getServerBaseForRequest() + "/";
}
private void handleNamelessSearchMethodBinding(CapabilityStatementRestResourceComponent resource, RuntimeResourceDefinition def, TreeSet<String> includes,
SearchMethodBinding searchMethodBinding, RequestDetails theRequestDetails) {
includes.addAll(searchMethodBinding.getIncludes());
List<IParameter> params = searchMethodBinding.getParameters();
List<SearchParameter> searchParameters = new ArrayList<>();
for (IParameter nextParameter : params) {
if ((nextParameter instanceof SearchParameter)) {
searchParameters.add((SearchParameter) nextParameter);
}
}
sortSearchParameters(searchParameters);
if (!searchParameters.isEmpty()) {
Set<String> paramNames = new HashSet<>();
for (SearchParameter nextParameter : searchParameters) {
if (nextParameter.getParamType() == null) {
ourLog.warn("SearchParameter {}:{} does not declare a type - Not exporting in CapabilityStatement", def.getName(), nextParameter.getName());
continue;
}
String nextParamName = nextParameter.getName();
String nextParamUnchainedName = nextParamName;
if (nextParamName.contains(".")) {
nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.'));
}
if (!paramNames.add(nextParamUnchainedName)) {
continue;
}
String nextParamDescription = nextParameter.getDescription();
/*
* If the parameter has no description, default to the one from the resource
*/
if (StringUtils.isBlank(nextParamDescription)) {
RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName);
if (paramDef != null) {
nextParamDescription = paramDef.getDescription();
}
}
CapabilityStatementRestResourceSearchParamComponent param = resource.addSearchParam();
String typeCode = nextParameter.getParamType().getCode();
param.getTypeElement().setValueAsString(typeCode);
param.setName(nextParamUnchainedName);
param.setDocumentation(nextParamDescription);
}
}
}
@Read(type = OperationDefinition.class)
public OperationDefinition readOperationDefinition(@IdParam IdType theId, RequestDetails theRequestDetails) {
if (theId == null || theId.hasIdPart() == false) {
throw new ResourceNotFoundException(theId);
}
RestfulServerConfiguration configuration = getServerConfiguration(theRequestDetails);
Bindings bindings = configuration.provideBindings();
List<OperationMethodBinding> operationBindings = bindings.getOperationNameToBindings().get(theId.getIdPart());
if (operationBindings != null && !operationBindings.isEmpty()) {
return readOperationDefinitionForOperation(operationBindings);
}
List<SearchMethodBinding> searchBindings = bindings.getSearchNameToBindings().get(theId.getIdPart());
if (searchBindings != null && !searchBindings.isEmpty()) {
return readOperationDefinitionForNamedSearch(searchBindings);
}
throw new ResourceNotFoundException(theId);
}
private OperationDefinition readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) {
OperationDefinition op = new OperationDefinition();
op.setStatus(PublicationStatus.ACTIVE);
op.setKind(OperationKind.QUERY);
op.setAffectsState(false);
op.setSystem(false);
op.setType(false);
op.setInstance(false);
Set<String> inParams = new HashSet<>();
for (SearchMethodBinding binding : bindings) {
if (isNotBlank(binding.getDescription())) {
op.setDescription(binding.getDescription());
}
if (isBlank(binding.getResourceProviderResourceName())) {
op.setSystem(true);
} else {
op.setType(true);
op.addResourceElement().setValue(binding.getResourceProviderResourceName());
}
op.setCode(binding.getQueryName());
for (IParameter nextParamUntyped : binding.getParameters()) {
if (nextParamUntyped instanceof SearchParameter) {
SearchParameter nextParam = (SearchParameter) nextParamUntyped;
if (!inParams.add(nextParam.getName())) {
continue;
}
OperationDefinitionParameterComponent param = op.addParameter();
param.setUse(OperationParameterUse.IN);
param.setType("string");
param.getSearchTypeElement().setValueAsString(nextParam.getParamType().getCode());
param.setMin(nextParam.isRequired() ? 1 : 0);
param.setMax("1");
param.setName(nextParam.getName());
}
}
if (isBlank(op.getName())) {
if (isNotBlank(op.getDescription())) {
op.setName(op.getDescription());
} else {
op.setName(op.getCode());
}
}
}
return op;
}
private OperationDefinition readOperationDefinitionForOperation(List<OperationMethodBinding> bindings) {
OperationDefinition op = new OperationDefinition();
op.setStatus(PublicationStatus.ACTIVE);
op.setKind(OperationKind.OPERATION);
op.setAffectsState(false);
// We reset these to true below if we find a binding that can handle the level
op.setSystem(false);
op.setType(false);
op.setInstance(false);
Set<String> inParams = new HashSet<>();
Set<String> outParams = new HashSet<>();
for (OperationMethodBinding sharedDescription : bindings) {
if (isNotBlank(sharedDescription.getDescription())) {
op.setDescription(sharedDescription.getDescription());
}
if (sharedDescription.isCanOperateAtInstanceLevel()) {
op.setInstance(true);
}
if (sharedDescription.isCanOperateAtServerLevel()) {
op.setSystem(true);
}
if (sharedDescription.isCanOperateAtTypeLevel()) {
op.setType(true);
}
if (!sharedDescription.isIdempotent()) {
op.setAffectsState(!sharedDescription.isIdempotent());
}
op.setCode(sharedDescription.getName().substring(1));
if (sharedDescription.isCanOperateAtInstanceLevel()) {
op.setInstance(sharedDescription.isCanOperateAtInstanceLevel());
}
if (sharedDescription.isCanOperateAtServerLevel()) {
op.setSystem(sharedDescription.isCanOperateAtServerLevel());
}
if (isNotBlank(sharedDescription.getResourceName())) {
op.addResourceElement().setValue(sharedDescription.getResourceName());
}
for (IParameter nextParamUntyped : sharedDescription.getParameters()) {
if (nextParamUntyped instanceof OperationParameter) {
OperationParameter nextParam = (OperationParameter) nextParamUntyped;
OperationDefinitionParameterComponent param = op.addParameter();
if (!inParams.add(nextParam.getName())) {
continue;
}
param.setUse(OperationParameterUse.IN);
if (nextParam.getParamType() != null) {
param.setType(nextParam.getParamType());
}
if (nextParam.getSearchParamType() != null) {
param.getSearchTypeElement().setValueAsString(nextParam.getSearchParamType());
}
param.setMin(nextParam.getMin());
param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
param.setName(nextParam.getName());
}
}
for (ReturnType nextParam : sharedDescription.getReturnParams()) {
if (!outParams.add(nextParam.getName())) {
continue;
}
OperationDefinitionParameterComponent param = op.addParameter();
param.setUse(OperationParameterUse.OUT);
if (nextParam.getType() != null) {
param.setType(nextParam.getType());
}
param.setMin(nextParam.getMin());
param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
param.setName(nextParam.getName());
}
}
if (isBlank(op.getName())) {
if (isNotBlank(op.getDescription())) {
op.setName(op.getDescription());
} else {
op.setName(op.getCode());
}
}
if (op.hasSystem() == false) {
op.setSystem(false);
}
if (op.hasInstance() == false) {
op.setInstance(false);
}
return op;
}
/**
* Sets the cache property (default is true). If set to true, the same response will be returned for each invocation.
* <p>
* See the class documentation for an important note if you are extending this class
* </p>
*
* @deprecated Since 4.0.0 - This method no longer does anything
*/
@Deprecated
public ServerCapabilityStatementProvider setCache(boolean theCache) {
return this;
}
@Override
public void setRestfulServer(RestfulServer theRestfulServer) {
// ignore
}
private void sortSearchParameters(List<SearchParameter> searchParameters) {
Collections.sort(searchParameters, new Comparator<SearchParameter>() {
@Override
public int compare(SearchParameter theO1, SearchParameter theO2) {
if (theO1.isRequired() == theO2.isRequired()) {
return theO1.getName().compareTo(theO2.getName());
}
if (theO1.isRequired()) {
return -1;
}
return 1;
}
});
}
}

View File

@ -1,4 +1,22 @@
// hapi-fhir/hapi-fhir-structures-r4$ mvn -Dtest=ca.uhn.fhir.parser.RDFParserTest test
/** test ca.uhn.fhir.parser
* This parses the FHIR JSON examples and round-trips them through RDF.
* See testRDFRoundTrip() for details.
*
* editors:
* - Eric Prud'hommeaux <eric@w3.org>
*
* TODO:
* - Consider sharing the FHIR JSON examples in
* ../../../../../resources/rdf-test-input/ with other HAPI tests and move to
* resources/examples/JSON.
* - Add FHIR RDF examples and validate graph isomorphism with examples/RDF (or
* examples/Turtle).
*
* see also:
* ../../../../../../../../hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java
* run test:
* hapi-fhir/hapi-fhir-structures-r4$ mvn -Dtest=ca.uhn.fhir.parser.RDFParserTest test
*/
package ca.uhn.fhir.parser;

View File

@ -1,31 +1,32 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.MyPatientWithExtensions;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.DateUtils;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Patient;
import org.junit.jupiter.api.Test;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
@ -34,30 +35,34 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ReadDstu3Test {
public class ReadR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadR4Test.class);
private static CloseableHttpClient ourClient;
private final FhirContext myCtx = FhirContext.forCached(FhirVersionEnum.R4);
@RegisterExtension
public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(myCtx);
private int myPort;
private static FhirContext ourCtx = FhirContext.forDstu3();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadDstu3Test.class);
private static int ourPort;
private static Server ourServer;
@BeforeEach
public void before() {
myPort = myRestfulServerExtension.getPort();
}
@Test
public void testRead() throws Exception {
myRestfulServerExtension.getRestfulServer().registerProvider(new PatientProvider());
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_format=xml&_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2?_format=xml&_pretty=true");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response was:\n{}", responseContent);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(null, status.getFirstHeader(Constants.HEADER_LOCATION));
assertEquals("http://localhost:" + myPort + "/Patient/2/_history/2", status.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(null, status.getFirstHeader(Constants.HEADER_LOCATION));
assertEquals("http://localhost:" + ourPort + "/Patient/2/_history/2", status.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
assertThat(responseContent, stringContainsInOrder(
assertThat(responseContent, stringContainsInOrder(
"<Patient xmlns=\"http://hl7.org/fhir\">",
" <id value=\"2\"/>",
" <meta>",
@ -67,14 +72,44 @@ public class ReadDstu3Test {
" <valueDate value=\"2011-01-01\"/>",
" </modifierExtension>",
"</Patient>"));
}
}
@Test
public void testReadUsingPlainProvider() throws Exception {
myRestfulServerExtension.getRestfulServer().registerProvider(new PlainGenericPatientProvider());
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2?_format=xml&_pretty=true");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(null, status.getFirstHeader(Constants.HEADER_LOCATION));
assertEquals("http://localhost:" + myPort + "/Patient/2/_history/2", status.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
assertThat(responseContent, stringContainsInOrder(
"<Patient xmlns=\"http://hl7.org/fhir\">",
" <id value=\"2\"/>",
" <meta>",
" <profile value=\"http://example.com/StructureDefinition/patient_with_extensions\"/>",
" </meta>",
" <modifierExtension url=\"http://example.com/ext/date\">",
" <valueDate value=\"2011-01-01\"/>",
" </modifierExtension>",
"</Patient>"));
}
}
@Test
public void testInvalidQueryParamsInRead() throws Exception {
myRestfulServerExtension.getRestfulServer().registerProvider(new PatientProvider());
CloseableHttpResponse status;
HttpGet httpGet;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_contained=both&_format=xml&_pretty=true");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2?_contained=both&_format=xml&_pretty=true");
status = ourClient.execute(httpGet);
try (InputStream inputStream = status.getEntity().getContent()) {
assertEquals(400, status.getStatusLine().getStatusCode());
@ -91,7 +126,7 @@ public class ReadDstu3Test {
));
}
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_containedType=contained&_format=xml&_pretty=true");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2?_containedType=contained&_format=xml&_pretty=true");
status = ourClient.execute(httpGet);
try (InputStream inputStream = status.getEntity().getContent()) {
assertEquals(400, status.getStatusLine().getStatusCode());
@ -108,7 +143,7 @@ public class ReadDstu3Test {
));
}
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_count=10&_format=xml&_pretty=true");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2?_count=10&_format=xml&_pretty=true");
status = ourClient.execute(httpGet);
try (InputStream inputStream = status.getEntity().getContent()) {
assertEquals(400, status.getStatusLine().getStatusCode());
@ -125,7 +160,7 @@ public class ReadDstu3Test {
));
}
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_include=Patient:organization&_format=xml&_pretty=true");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2?_include=Patient:organization&_format=xml&_pretty=true");
status = ourClient.execute(httpGet);
try (InputStream inputStream = status.getEntity().getContent()) {
assertEquals(400, status.getStatusLine().getStatusCode());
@ -142,7 +177,7 @@ public class ReadDstu3Test {
));
}
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_revinclude=Provenance:target&_format=xml&_pretty=true");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2?_revinclude=Provenance:target&_format=xml&_pretty=true");
status = ourClient.execute(httpGet);
try (InputStream inputStream = status.getEntity().getContent()) {
assertEquals(400, status.getStatusLine().getStatusCode());
@ -159,7 +194,7 @@ public class ReadDstu3Test {
));
}
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_sort=family&_format=xml&_pretty=true");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2?_sort=family&_format=xml&_pretty=true");
status = ourClient.execute(httpGet);
try (InputStream inputStream = status.getEntity().getContent()) {
assertEquals(400, status.getStatusLine().getStatusCode());
@ -176,7 +211,7 @@ public class ReadDstu3Test {
));
}
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_total=accurate&_format=xml&_pretty=true");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2?_total=accurate&_format=xml&_pretty=true");
status = ourClient.execute(httpGet);
try (InputStream inputStream = status.getEntity().getContent()) {
assertEquals(400, status.getStatusLine().getStatusCode());
@ -196,77 +231,39 @@ public class ReadDstu3Test {
@Test
public void testIfModifiedSince() throws Exception {
myRestfulServerExtension.getRestfulServer().registerProvider(new PatientProvider());
CloseableHttpResponse status;
HttpGet httpGet;
// Fixture was last modified at 2012-01-01T12:12:12Z
// thus it hasn't changed after the later time of 2012-01-01T13:00:00Z
// so we expect a 304 (Not Modified)
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2");
httpGet.addHeader(Constants.HEADER_IF_MODIFIED_SINCE, DateUtils.formatDate(new InstantDt("2012-01-01T13:00:00Z").getValue()));
status = ourClient.execute(httpGet);
try {
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(304, status.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(status);
}
// Fixture was last modified at 2012-01-01T12:12:12Z
// thus it hasn't changed after the same time of 2012-01-01T12:12:12Z
// so we expect a 304 (Not Modified)
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2");
httpGet.addHeader(Constants.HEADER_IF_MODIFIED_SINCE, DateUtils.formatDate(new InstantDt("2012-01-01T12:12:12Z").getValue()));
status = ourClient.execute(httpGet);
try {
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(304, status.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(status);
}
// Fixture was last modified at 2012-01-01T12:12:12Z
// thus it has changed after the earlier time of 2012-01-01T10:00:00Z
// so we expect a 200
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient/2");
httpGet.addHeader(Constants.HEADER_IF_MODIFIED_SINCE, DateUtils.formatDate(new InstantDt("2012-01-01T10:00:00Z").getValue()));
status = ourClient.execute(httpGet);
try {
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(status);
}
}
@AfterAll
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeAll
public static void beforeClass() throws Exception {
ourServer = new Server(0);
PatientProvider patientProvider = new PatientProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
JettyUtil.startServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class PatientProvider implements IResourceProvider {
@Override
@ -288,4 +285,33 @@ public class ReadDstu3Test {
}
public static class PlainGenericPatientProvider {
@Read(version = true, typeName = "Patient")
public IBaseResource read(@IdParam IIdType theIdParam) {
MyPatientWithExtensions p0 = new MyPatientWithExtensions();
p0.getMeta().getLastUpdatedElement().setValueAsString("2012-01-01T12:12:12Z");
p0.setId(theIdParam);
if (theIdParam.hasVersionIdPart() == false) {
p0.setIdElement(p0.getIdElement().withVersion("2"));
}
p0.setDateExt(new DateType("2011-01-01"));
return p0;
}
}
@BeforeAll
public static void beforeClass() throws Exception {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
@AfterAll
public static void afterClass() throws IOException {
ourClient.close();
}
}

View File

@ -0,0 +1,169 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.SearchPreferHandlingInterceptor;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class SearchPreferHandlingInterceptorTest {
private final FhirContext myCtx = FhirContext.forCached(FhirVersionEnum.R4);
@RegisterExtension
public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(myCtx)
.registerProvider(new DummyPatientResourceProvider())
.withServer(t -> t.setDefaultResponseEncoding(EncodingEnum.JSON))
.withServer(t -> t.setPagingProvider(new FifoMemoryPagingProvider(10)))
.registerInterceptor(new SearchPreferHandlingInterceptor());
private int myPort;
private IGenericClient myClient;
@BeforeEach
public void before() {
myClient = myRestfulServerExtension.getFhirClient();
myPort = myRestfulServerExtension.getPort();
}
@Test
public void testSearchWithInvalidParam_NoHeader() {
try {
myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.prettyPrint()
.returnBundle(Bundle.class)
.encodedJson()
.execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [identifier]"));
}
}
@Test
public void testSearchWithUnknownResourceType() throws IOException {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
try (CloseableHttpResponse result = client.execute(new HttpGet("http://localhost:" + myPort + "/BadResource?foo=bar"))) {
assertEquals(404, result.getStatusLine().getStatusCode());
String response = IOUtils.toString(result.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(response, containsString("Unknown resource type 'BadResource' - Server knows how to handle: [Patient, OperationDefinition]"));
}
}
}
@Test
public void testSearchWithInvalidParam_StrictHeader() {
try {
myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.withAdditionalHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_HANDLING + "=" + Constants.HEADER_PREFER_HANDLING_STRICT)
.prettyPrint()
.returnBundle(Bundle.class)
.encodedJson()
.execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [identifier]"));
}
}
@Test
public void testSearchWithInvalidParam_UnrelatedPreferHeader() {
try {
myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.withAdditionalHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_REPRESENTATION)
.prettyPrint()
.returnBundle(Bundle.class)
.encodedJson()
.execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [identifier]"));
}
}
@Test
public void testSearchWithInvalidParam_LenientHeader() {
Bundle outcome = myClient
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.and(Patient.IDENTIFIER.exactly().codes("BLAH"))
.prettyPrint()
.returnBundle(Bundle.class)
.withAdditionalHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_HANDLING + "=" + Constants.HEADER_PREFER_HANDLING_LENIENT)
.encodedJson()
.execute();
assertEquals(200, outcome.getTotal());
assertEquals("http://localhost:" + myPort + "/Patient?_format=json&_pretty=true&identifier=BLAH", outcome.getLink(Constants.LINK_SELF).getUrl());
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@SuppressWarnings("rawtypes")
@Search()
public List search(
@OptionalParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
ArrayList<Patient> retVal = new ArrayList<>();
for (int i = 0; i < 200; i++) {
Patient patient = new Patient();
patient.getIdElement().setValue("Patient/" + i + "/_history/222");
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(patient, BundleEntrySearchModeEnum.INCLUDE.getCode());
patient.addName(new HumanName().setFamily("FAMILY"));
patient.setActive(true);
retVal.add(patient);
}
return retVal;
}
}
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
@ -17,6 +18,7 @@ import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.validation.FhirValidator;
@ -43,6 +45,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@ -63,17 +66,22 @@ public class SearchR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchR4Test.class);
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static TokenAndListParam ourIdentifiers;
private static String ourLastMethod;
private static int ourPort;
private static Server ourServer;
private final FhirContext myCtx = FhirContext.forCached(FhirVersionEnum.R4);
@RegisterExtension
public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(myCtx)
.registerProvider(new DummyPatientResourceProvider())
.registerProvider(new DummyMedicationRequestResourceProvider())
.withServer(t -> t.setDefaultResponseEncoding(EncodingEnum.JSON))
.withServer(t -> t.setPagingProvider(new FifoMemoryPagingProvider(10)));
private int myPort;
@BeforeEach
public void before() {
ourLastMethod = null;
ourIdentifiers = null;
myPort = myRestfulServerExtension.getPort();
}
private Bundle executeSearchAndValidateHasLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException {
@ -93,7 +101,7 @@ public class SearchR4Test {
assertEquals(200, status.getStatusLine().getStatusCode());
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
assertEquals(theExpectEncoding, ct);
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
bundle = ct.newParser(myCtx).parseResource(Bundle.class, responseContent);
validate(bundle);
}
return bundle;
@ -104,7 +112,7 @@ public class SearchR4Test {
*/
@Test
public void testPageRequestCantTriggerSearchAccidentally() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?" + Constants.PARAM_PAGINGACTION + "=12345");
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?" + Constants.PARAM_PAGINGACTION + "=12345");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
@ -119,7 +127,7 @@ public class SearchR4Test {
*/
@Test
public void testSummaryCount() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&"+Constants.PARAM_SUMMARY + "=" + SummaryEnum.COUNT.getCode());
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar&" + Constants.PARAM_SUMMARY + "=" + SummaryEnum.COUNT.getCode());
Bundle bundle = executeSearch(httpGet, EncodingEnum.JSON);
ourLog.info(toJson(bundle));
assertEquals(200, bundle.getTotal());
@ -128,7 +136,6 @@ public class SearchR4Test {
}
@Test
public void testPagingPreservesElements() throws Exception {
HttpGet httpGet;
@ -137,7 +144,7 @@ public class SearchR4Test {
String linkSelf;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_elements=name&_elements:exclude=birthDate,active");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar&_elements=name&_elements:exclude=birthDate,active");
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
@ -182,7 +189,7 @@ public class SearchR4Test {
Bundle bundle;
// No include specified
httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest");
httpGet = new HttpGet("http://localhost:" + myPort + "/MedicationRequest");
bundle = executeAndReturnBundle(httpGet);
assertEquals(1, bundle.getEntry().size());
}
@ -196,7 +203,7 @@ public class SearchR4Test {
Bundle bundle;
// * include specified
httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest?_include=" + UrlUtil.escapeUrlParam("*"));
httpGet = new HttpGet("http://localhost:" + myPort + "/MedicationRequest?_include=" + UrlUtil.escapeUrlParam("*"));
bundle = executeAndReturnBundle(httpGet);
assertEquals(2, bundle.getEntry().size());
}
@ -210,7 +217,7 @@ public class SearchR4Test {
Bundle bundle;
// MedicationRequest:medication include specified
httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest?_include=" + UrlUtil.escapeUrlParam(MedicationRequest.INCLUDE_MEDICATION.getValue()));
httpGet = new HttpGet("http://localhost:" + myPort + "/MedicationRequest?_include=" + UrlUtil.escapeUrlParam(MedicationRequest.INCLUDE_MEDICATION.getValue()));
bundle = executeAndReturnBundle(httpGet);
assertEquals(2, bundle.getEntry().size());
@ -221,7 +228,7 @@ public class SearchR4Test {
try (CloseableHttpResponse status = ourClient.execute(theHttpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
assertEquals(200, status.getStatusLine().getStatusCode());
bundle = ourCtx.newJsonParser().parseResource(Bundle.class, responseContent);
bundle = myCtx.newJsonParser().parseResource(Bundle.class, responseContent);
}
return bundle;
}
@ -233,7 +240,7 @@ public class SearchR4Test {
Bundle bundle;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW);
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
@ -265,7 +272,7 @@ public class SearchR4Test {
Bundle bundle;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar&_format=json");
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), containsString("active"));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
@ -298,7 +305,7 @@ public class SearchR4Test {
Bundle bundle;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar");
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
@ -330,7 +337,7 @@ public class SearchR4Test {
Bundle bundle;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar");
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
@ -366,7 +373,7 @@ public class SearchR4Test {
Bundle bundle;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml");
httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar&_format=xml");
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
@ -393,11 +400,11 @@ public class SearchR4Test {
@Test
public void testSearchNormal() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
validate(ourCtx.newJsonParser().parseResource(responseContent));
validate(myCtx.newJsonParser().parseResource(responseContent));
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("search", ourLastMethod);
@ -410,7 +417,7 @@ public class SearchR4Test {
@Test
public void testRequestIdGeneratedAndReturned() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String requestId = status.getFirstHeader(Constants.HEADER_REQUEST_ID).getValue();
@ -420,7 +427,7 @@ public class SearchR4Test {
@Test
public void testRequestIdSuppliedAndReturned() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
httpGet.addHeader(Constants.HEADER_REQUEST_ID, "help im a bug");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
@ -431,7 +438,7 @@ public class SearchR4Test {
@Test
public void testRequestIdSuppliedAndReturned_Invalid() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
httpGet.addHeader(Constants.HEADER_REQUEST_ID, "help i'm a bug");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
@ -442,13 +449,13 @@ public class SearchR4Test {
@Test
public void testSearchWithInvalidChain() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar");
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Patient?identifier.chain=foo%7Cbar");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent);
OperationOutcome oo = (OperationOutcome) myCtx.newJsonParser().parseResource(responseContent);
assertEquals(
"Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)",
oo.getIssueFirstRep().getDiagnostics());
@ -458,7 +465,7 @@ public class SearchR4Test {
@Test
public void testSearchWithPostAndInvalidParameters() {
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
IGenericClient client = myCtx.newRestfulGenericClient("http://localhost:" + myPort);
LoggingInterceptor interceptor = new LoggingInterceptor();
interceptor.setLogRequestSummary(true);
interceptor.setLogRequestBody(true);
@ -485,14 +492,14 @@ public class SearchR4Test {
}
private String toJson(Bundle theBundle) {
return ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theBundle);
return myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theBundle);
}
protected void validate(IBaseResource theResource) {
FhirValidator validatorModule = ourCtx.newValidator();
FhirValidator validatorModule = myCtx.newValidator();
ValidationResult result = validatorModule.validateWithResult(theResource);
if (!result.isSuccessful()) {
fail(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome()));
fail(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome()));
}
}
@ -560,34 +567,15 @@ public class SearchR4Test {
@AfterAll
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
TestUtil.clearAllStaticFieldsForUnitTest();
ourClient.close();
}
@BeforeAll
public static void beforeClass() throws Exception {
ourServer = new Server(0);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
DummyMedicationRequestResourceProvider medRequestProvider = new DummyMedicationRequestResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
servlet.setResourceProviders(patientProvider, medRequestProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
JettyUtil.startServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
}

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.parser.DataFormatException;
import com.google.common.collect.Lists;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseExtension;
@ -67,7 +68,116 @@ import static org.mockito.Mockito.when;
public class FhirTerserR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirTerserR4Test.class);
private FhirContext myCtx = FhirContext.forCached(FhirVersionEnum.R4);
private final FhirContext myCtx = FhirContext.forCached(FhirVersionEnum.R4);
@Test
public void testAddElement() {
Patient patient = new Patient();
IBase family = myCtx.newTerser().addElement(patient, "Patient.name.family");
assertEquals(1, patient.getName().size());
assertSame(family, patient.getName().get(0).getFamilyElement());
}
@Test
public void testAddElementWithValue() {
Patient patient = new Patient();
IBase family = myCtx.newTerser().addElement(patient, "Patient.name.family", "FOO");
assertEquals(1, patient.getName().size());
assertSame(family, patient.getName().get(0).getFamilyElement());
assertEquals("FOO", patient.getName().get(0).getFamilyElement().getValue());
}
@Test
public void testAddElementWithValue_NonPrimitivePath() {
Patient patient = new Patient();
try {
myCtx.newTerser().addElement(patient, "Patient.name", "FOO");
fail();
} catch (DataFormatException e) {
assertEquals("Element at path Patient.name is not a primitive datatype. Found: HumanName", e.getMessage());
}
}
@Test
public void testAddElements_NonRepeatingPath() {
Patient patient = new Patient();
try {
myCtx.newTerser().addElements(patient, "Patient.name.family", Lists.newArrayList("FOO", "BAR"));
fail();
} catch (DataFormatException e) {
assertEquals("Can not add multiple values at path Patient.name.family: Element does not repeat", e.getMessage());
}
}
@Test
public void testAddElementReusesExisting() {
Patient patient = new Patient();
StringType existingFamily = patient.addName().getFamilyElement();
patient.addName().setFamily("FAM2");
patient.addName().setFamily("FAM3");
IBase family = myCtx.newTerser().addElement(patient, "Patient.name.family");
assertEquals(3, patient.getName().size());
assertTrue(existingFamily == patient.getName().get(0).getFamilyElement());
assertSame(family, patient.getName().get(0).getFamilyElement());
}
@Test
public void testAddElementCantReuseExistingBecauseItIsNotEmpty() {
Patient patient = new Patient();
patient.addName().setFamily("FAM1");
patient.addName().setFamily("FAM2");
patient.addName().setFamily("FAM3");
try {
myCtx.newTerser().addElement(patient, "Patient.name.family");
fail();
} catch (DataFormatException e) {
assertEquals("Element at path Patient.name.family is not repeatable and not empty", e.getMessage());
}
}
@Test
public void testAddElementInvalidPath() {
Patient patient = new Patient();
// So much foo....
try {
myCtx.newTerser().addElement(patient, "foo");
fail();
} catch (DataFormatException e) {
assertEquals("Invalid path foo: Element of type Patient has no child named foo. Valid names: active, address, birthDate, communication, contact, contained, deceased, extension, gender, generalPractitioner, id, identifier, implicitRules, language, link, managingOrganization, maritalStatus, meta, modifierExtension, multipleBirth, name, photo, telecom, text", e.getMessage());
}
try {
myCtx.newTerser().addElement(patient, "Patient.foo");
fail();
} catch (DataFormatException e) {
assertEquals("Invalid path Patient.foo: Element of type Patient has no child named foo. Valid names: active, address, birthDate, communication, contact, contained, deceased, extension, gender, generalPractitioner, id, identifier, implicitRules, language, link, managingOrganization, maritalStatus, meta, modifierExtension, multipleBirth, name, photo, telecom, text", e.getMessage());
}
try {
myCtx.newTerser().addElement(patient, "Patient.name.foo");
fail();
} catch (DataFormatException e) {
assertEquals("Invalid path Patient.name.foo: Element of type HumanName has no child named foo. Valid names: extension, family, given, id, period, prefix, suffix, text, use", e.getMessage());
}
try {
myCtx.newTerser().addElement(patient, "Patient.name.family.foo");
fail();
} catch (DataFormatException e) {
assertEquals("Invalid path Patient.name.family.foo: Element of type HumanName has no child named family (this is a primitive type)", e.getMessage());
}
}
@Test
public void testContainResourcesWithModify() {

View File

@ -2,12 +2,12 @@ package org.hl7.fhir.r5.hapi.ctx;
import ca.uhn.fhir.rest.api.server.IFhirVersionServer;
import ca.uhn.fhir.rest.server.RestfulServer;
import org.hl7.fhir.r5.hapi.rest.server.ServerCapabilityStatementProvider;
import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
public class FhirServerR5 implements IFhirVersionServer {
@Override
public ServerCapabilityStatementProvider createServerConformanceProvider(RestfulServer theServer) {
return new ServerCapabilityStatementProvider();
}
@Override
public ServerCapabilityStatementProvider createServerConformanceProvider(RestfulServer theServer) {
return new ServerCapabilityStatementProvider(theServer);
}
}

View File

@ -1,606 +0,0 @@
package org.hl7.fhir.r5.hapi.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.Bindings;
import ca.uhn.fhir.rest.server.IServerConformanceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServerConfiguration;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.IParameter;
import ca.uhn.fhir.rest.server.method.OperationMethodBinding;
import ca.uhn.fhir.rest.server.method.OperationMethodBinding.ReturnType;
import ca.uhn.fhir.rest.server.method.OperationParameter;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.method.SearchParameter;
import ca.uhn.fhir.rest.server.util.BaseServerCapabilityStatementProvider;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.ConditionalDeleteStatus;
import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.SystemRestfulInteraction;
import org.hl7.fhir.r5.model.CapabilityStatement.TypeRestfulInteraction;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.OperationDefinition;
import org.hl7.fhir.r5.model.OperationDefinition.OperationDefinitionParameterComponent;
import org.hl7.fhir.r5.model.OperationDefinition.OperationKind;
import org.hl7.fhir.r5.model.ResourceType;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR Structures - DSTU2 (FHIR v1.0.0)
* %%
* Copyright (C) 2014 - 2015 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%
*/
/**
* Server FHIR Provider which serves the conformance statement for a RESTful server implementation
*
* <p>
* Note: This class is safe to extend, but it is important to note that the same instance of {@link CapabilityStatement} is always returned unless {@link #setCache(boolean)} is called with a value of
* <code>false</code>. This means that if you are adding anything to the returned conformance instance on each call you should call <code>setCache(false)</code> in your provider constructor.
* </p>
*/
public class ServerCapabilityStatementProvider extends BaseServerCapabilityStatementProvider implements IServerConformanceProvider<CapabilityStatement> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProvider.class);
private String myPublisher = "Not provided";
/**
* No-arg constructor and setter so that the ServerConformanceProvider can be Spring-wired with the RestfulService avoiding the potential reference cycle that would happen.
*/
public ServerCapabilityStatementProvider() {
super();
}
/**
* Constructor - This is intended only for JAX-RS server
*/
public ServerCapabilityStatementProvider(RestfulServerConfiguration theServerConfiguration) {
super(theServerConfiguration);
}
private void checkBindingForSystemOps(CapabilityStatementRestComponent rest, Set<SystemRestfulInteraction> systemOps, BaseMethodBinding<?> nextMethodBinding) {
if (nextMethodBinding.getRestOperationType() != null) {
String sysOpCode = nextMethodBinding.getRestOperationType().getCode();
if (sysOpCode != null) {
SystemRestfulInteraction sysOp;
try {
sysOp = SystemRestfulInteraction.fromCode(sysOpCode);
} catch (FHIRException e) {
return;
}
if (sysOp == null) {
return;
}
if (systemOps.contains(sysOp) == false) {
systemOps.add(sysOp);
rest.addInteraction().setCode(sysOp);
}
}
}
}
private DateTimeType conformanceDate(RequestDetails theRequestDetails) {
IPrimitiveType<Date> buildDate = getServerConfiguration(theRequestDetails).getConformanceDate();
if (buildDate != null && buildDate.getValue() != null) {
try {
return new DateTimeType(buildDate.getValueAsString());
} catch (DataFormatException e) {
// fall through
}
}
return DateTimeType.now();
}
/**
* Gets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
* value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
*/
public String getPublisher() {
return myPublisher;
}
/**
* Sets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
* value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
*/
public void setPublisher(String thePublisher) {
myPublisher = thePublisher;
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
@Override
@Metadata
public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
RestfulServerConfiguration configuration = getServerConfiguration(theRequestDetails);
Bindings bindings = configuration.provideBindings();
CapabilityStatement retVal = new CapabilityStatement();
retVal.setPublisher(myPublisher);
retVal.setDateElement(conformanceDate(theRequestDetails));
retVal.setFhirVersion(Enumerations.FHIRVersion.fromCode(FhirVersionEnum.R5.getFhirVersionString()));
ServletContext servletContext = (ServletContext) (theRequest == null ? null : theRequest.getAttribute(RestfulServer.SERVLET_CONTEXT_ATTRIBUTE));
String serverBase = configuration.getServerAddressStrategy().determineServerBase(servletContext, theRequest);
retVal
.getImplementation()
.setUrl(serverBase)
.setDescription(configuration.getImplementationDescription());
retVal.setKind(Enumerations.CapabilityStatementKind.INSTANCE);
retVal.getSoftware().setName(configuration.getServerName());
retVal.getSoftware().setVersion(configuration.getServerVersion());
retVal.addFormat(Constants.CT_FHIR_XML_NEW);
retVal.addFormat(Constants.CT_FHIR_JSON_NEW);
retVal.addFormat(Constants.FORMAT_JSON);
retVal.addFormat(Constants.FORMAT_XML);
retVal.setStatus(PublicationStatus.ACTIVE);
CapabilityStatementRestComponent rest = retVal.addRest();
rest.setMode(Enumerations.RestfulCapabilityMode.SERVER);
Set<SystemRestfulInteraction> systemOps = new HashSet<>();
Set<String> operationNames = new HashSet<>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = configuration.collectMethodBindings();
Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype = configuration.getNameToSharedSupertype();
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
if (nextEntry.getKey().isEmpty() == false) {
Set<TypeRestfulInteraction> resourceOps = new HashSet<>();
CapabilityStatementRestResourceComponent resource = rest.addResource();
String resourceName = nextEntry.getKey();
RuntimeResourceDefinition def;
FhirContext context = configuration.getFhirContext();
if (resourceNameToSharedSupertype.containsKey(resourceName)) {
def = context.getResourceDefinition(resourceNameToSharedSupertype.get(resourceName));
} else {
def = context.getResourceDefinition(resourceName);
}
resource.getTypeElement().setValue(def.getName());
resource.getProfileElement().setValue((def.getResourceProfile(serverBase)));
TreeSet<String> includes = new TreeSet<>();
// Map<String, CapabilityStatement.RestResourceSearchParam> nameToSearchParam = new HashMap<String,
// CapabilityStatement.RestResourceSearchParam>();
for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
if (nextMethodBinding.getRestOperationType() != null) {
String resOpCode = nextMethodBinding.getRestOperationType().getCode();
if (resOpCode != null) {
TypeRestfulInteraction resOp;
try {
resOp = TypeRestfulInteraction.fromCode(resOpCode);
} catch (Exception e) {
resOp = null;
}
if (resOp != null) {
if (resourceOps.contains(resOp) == false) {
resourceOps.add(resOp);
resource.addInteraction().setCode(resOp);
}
if ("vread".equals(resOpCode)) {
// vread implies read
resOp = TypeRestfulInteraction.READ;
if (resourceOps.contains(resOp) == false) {
resourceOps.add(resOp);
resource.addInteraction().setCode(resOp);
}
}
if (nextMethodBinding.isSupportsConditional()) {
switch (resOp) {
case CREATE:
resource.setConditionalCreate(true);
break;
case DELETE:
if (nextMethodBinding.isSupportsConditionalMultiple()) {
resource.setConditionalDelete(ConditionalDeleteStatus.MULTIPLE);
} else {
resource.setConditionalDelete(ConditionalDeleteStatus.SINGLE);
}
break;
case UPDATE:
resource.setConditionalUpdate(true);
break;
default:
break;
}
}
}
}
}
checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
if (nextMethodBinding instanceof SearchMethodBinding) {
SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
if (methodBinding.getQueryName() != null) {
String queryName = bindings.getNamedSearchMethodBindingToName().get(methodBinding);
if (operationNames.add(queryName)) {
rest.addOperation().setName(methodBinding.getQueryName()).setDefinition((getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + queryName));
}
} else {
handleNamelessSearchMethodBinding(resource, def, includes, (SearchMethodBinding) nextMethodBinding, theRequestDetails);
}
} else if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
String opName = bindings.getOperationBindingToName().get(methodBinding);
if (operationNames.add(opName)) {
// Only add each operation (by name) once
rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition((getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + opName));
}
}
resource.getInteraction().sort(new Comparator<ResourceInteractionComponent>() {
@Override
public int compare(ResourceInteractionComponent theO1, ResourceInteractionComponent theO2) {
TypeRestfulInteraction o1 = theO1.getCode();
TypeRestfulInteraction o2 = theO2.getCode();
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return 1;
}
if (o2 == null) {
return -1;
}
return o1.ordinal() - o2.ordinal();
}
});
}
for (String nextInclude : includes) {
resource.addSearchInclude(nextInclude);
}
} else {
for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
String opName = bindings.getOperationBindingToName().get(methodBinding);
if (operationNames.add(opName)) {
ourLog.debug("Found bound operation: {}", opName);
rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition((getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + opName));
}
}
}
}
}
return retVal;
}
protected String getOperationDefinitionPrefix(RequestDetails theRequestDetails) {
if (theRequestDetails == null) {
return "";
}
return theRequestDetails.getServerBaseForRequest() + "/";
}
private void handleNamelessSearchMethodBinding(CapabilityStatementRestResourceComponent resource, RuntimeResourceDefinition def, TreeSet<String> includes,
SearchMethodBinding searchMethodBinding, RequestDetails theRequestDetails) {
includes.addAll(searchMethodBinding.getIncludes());
List<IParameter> params = searchMethodBinding.getParameters();
List<SearchParameter> searchParameters = new ArrayList<>();
for (IParameter nextParameter : params) {
if ((nextParameter instanceof SearchParameter)) {
searchParameters.add((SearchParameter) nextParameter);
}
}
sortSearchParameters(searchParameters);
if (!searchParameters.isEmpty()) {
// boolean allOptional = searchParameters.get(0).isRequired() == false;
//
// OperationDefinition query = null;
// if (!allOptional) {
// RestOperation operation = rest.addOperation();
// query = new OperationDefinition();
// operation.setDefinition(new ResourceReferenceDt(query));
// query.getDescriptionElement().setValue(searchMethodBinding.getDescription());
// query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName));
// for (String nextInclude : searchMethodBinding.getIncludes()) {
// query.addUndeclaredExtension(false, ExtensionConstants.QUERY_ALLOWED_INCLUDE, new StringDt(nextInclude));
// }
// }
for (SearchParameter nextParameter : searchParameters) {
String nextParamName = nextParameter.getName();
String chain = null;
String nextParamUnchainedName = nextParamName;
if (nextParamName.contains(".")) {
chain = nextParamName.substring(nextParamName.indexOf('.') + 1);
nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.'));
}
String nextParamDescription = nextParameter.getDescription();
/*
* If the parameter has no description, default to the one from the resource
*/
if (StringUtils.isBlank(nextParamDescription)) {
RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName);
if (paramDef != null) {
nextParamDescription = paramDef.getDescription();
}
}
CapabilityStatementRestResourceSearchParamComponent param = resource.addSearchParam();
param.setName(nextParamUnchainedName);
// if (StringUtils.isNotBlank(chain)) {
// param.addChain(chain);
// }
//
// if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
// for (String nextWhitelist : new TreeSet<String>(nextParameter.getQualifierWhitelist())) {
// if (nextWhitelist.startsWith(".")) {
// param.addChain(nextWhitelist.substring(1));
// }
// }
// }
param.setDocumentation(nextParamDescription);
if (nextParameter.getParamType() != null) {
param.getTypeElement().setValueAsString(nextParameter.getParamType().getCode());
}
for (Class<? extends IBaseResource> nextTarget : nextParameter.getDeclaredTypes()) {
RuntimeResourceDefinition targetDef = getServerConfiguration(theRequestDetails).getFhirContext().getResourceDefinition(nextTarget);
if (targetDef != null) {
ResourceType code;
try {
code = ResourceType.fromCode(targetDef.getName());
} catch (FHIRException e) {
code = null;
}
// if (code != null) {
// param.addTarget(targetDef.getName());
// }
}
}
}
}
}
@Read(type = OperationDefinition.class)
public OperationDefinition readOperationDefinition(@IdParam IdType theId, RequestDetails theRequestDetails) {
if (theId == null || theId.hasIdPart() == false) {
throw new ResourceNotFoundException(theId);
}
RestfulServerConfiguration configuration = getServerConfiguration(theRequestDetails);
Bindings bindings = configuration.provideBindings();
List<OperationMethodBinding> operationBindings = bindings.getOperationNameToBindings().get(theId.getIdPart());
if (operationBindings != null && !operationBindings.isEmpty()) {
return readOperationDefinitionForOperation(operationBindings);
}
List<SearchMethodBinding> searchBindings = bindings.getSearchNameToBindings().get(theId.getIdPart());
if (searchBindings != null && !searchBindings.isEmpty()) {
return readOperationDefinitionForNamedSearch(searchBindings);
}
throw new ResourceNotFoundException(theId);
}
private OperationDefinition readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) {
OperationDefinition op = new OperationDefinition();
op.setStatus(PublicationStatus.ACTIVE);
op.setKind(OperationKind.QUERY);
op.setAffectsState(false);
op.setSystem(false);
op.setType(false);
op.setInstance(false);
Set<String> inParams = new HashSet<>();
for (SearchMethodBinding binding : bindings) {
if (isNotBlank(binding.getDescription())) {
op.setDescription(binding.getDescription());
}
if (isBlank(binding.getResourceProviderResourceName())) {
op.setSystem(true);
} else {
op.setType(true);
op.addResourceElement().setValue(binding.getResourceProviderResourceName());
}
op.setCode(binding.getQueryName());
for (IParameter nextParamUntyped : binding.getParameters()) {
if (nextParamUntyped instanceof SearchParameter) {
SearchParameter nextParam = (SearchParameter) nextParamUntyped;
if (!inParams.add(nextParam.getName())) {
continue;
}
OperationDefinitionParameterComponent param = op.addParameter();
param.setUse(Enumerations.OperationParameterUse.IN);
param.setType(Enumerations.FHIRAllTypes.STRING);
param.getSearchTypeElement().setValueAsString(nextParam.getParamType().getCode());
param.setMin(nextParam.isRequired() ? 1 : 0);
param.setMax("1");
param.setName(nextParam.getName());
}
}
if (isBlank(op.getName())) {
if (isNotBlank(op.getDescription())) {
op.setName(op.getDescription());
} else {
op.setName(op.getCode());
}
}
}
return op;
}
private OperationDefinition readOperationDefinitionForOperation(List<OperationMethodBinding> bindings) {
OperationDefinition op = new OperationDefinition();
op.setStatus(PublicationStatus.ACTIVE);
op.setKind(OperationKind.OPERATION);
op.setAffectsState(false);
// We reset these to true below if we find a binding that can handle the level
op.setSystem(false);
op.setType(false);
op.setInstance(false);
Set<String> inParams = new HashSet<>();
Set<String> outParams = new HashSet<>();
for (OperationMethodBinding sharedDescription : bindings) {
if (isNotBlank(sharedDescription.getDescription())) {
op.setDescription(sharedDescription.getDescription());
}
if (sharedDescription.isCanOperateAtInstanceLevel()) {
op.setInstance(true);
}
if (sharedDescription.isCanOperateAtServerLevel()) {
op.setSystem(true);
}
if (sharedDescription.isCanOperateAtTypeLevel()) {
op.setType(true);
}
if (!sharedDescription.isIdempotent()) {
op.setAffectsState(!sharedDescription.isIdempotent());
}
op.setCode(sharedDescription.getName().substring(1));
if (sharedDescription.isCanOperateAtInstanceLevel()) {
op.setInstance(sharedDescription.isCanOperateAtInstanceLevel());
}
if (sharedDescription.isCanOperateAtServerLevel()) {
op.setSystem(sharedDescription.isCanOperateAtServerLevel());
}
if (isNotBlank(sharedDescription.getResourceName())) {
op.addResourceElement().setValue(sharedDescription.getResourceName());
}
for (IParameter nextParamUntyped : sharedDescription.getParameters()) {
if (nextParamUntyped instanceof OperationParameter) {
OperationParameter nextParam = (OperationParameter) nextParamUntyped;
OperationDefinitionParameterComponent param = op.addParameter();
if (!inParams.add(nextParam.getName())) {
continue;
}
param.setUse(Enumerations.OperationParameterUse.IN);
if (nextParam.getParamType() != null) {
param.setType(Enumerations.FHIRAllTypes.fromCode(nextParam.getParamType()));
}
if (nextParam.getSearchParamType() != null) {
param.getSearchTypeElement().setValueAsString(nextParam.getSearchParamType());
}
param.setMin(nextParam.getMin());
param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
param.setName(nextParam.getName());
}
}
for (ReturnType nextParam : sharedDescription.getReturnParams()) {
if (!outParams.add(nextParam.getName())) {
continue;
}
OperationDefinitionParameterComponent param = op.addParameter();
param.setUse(Enumerations.OperationParameterUse.OUT);
if (nextParam.getType() != null) {
param.setType(Enumerations.FHIRAllTypes.fromCode(nextParam.getType()));
}
param.setMin(nextParam.getMin());
param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
param.setName(nextParam.getName());
}
}
if (isBlank(op.getName())) {
if (isNotBlank(op.getDescription())) {
op.setName(op.getDescription());
} else {
op.setName(op.getCode());
}
}
if (op.hasSystem() == false) {
op.setSystem(false);
}
if (op.hasInstance() == false) {
op.setInstance(false);
}
return op;
}
@Override
public void setRestfulServer(RestfulServer theRestfulServer) {
// ignore
}
private void sortSearchParameters(List<SearchParameter> searchParameters) {
Collections.sort(searchParameters, new Comparator<SearchParameter>() {
@Override
public int compare(SearchParameter theO1, SearchParameter theO2) {
if (theO1.isRequired() == theO2.isRequired()) {
return theO1.getName().compareTo(theO2.getName());
}
if (theO1.isRequired()) {
return -1;
}
return 1;
}
});
}
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
@ -21,12 +22,12 @@ import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.IParameter;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.method.SearchParameter;
import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.ValidationResult;
import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.hapi.rest.server.ServerCapabilityStatementProvider;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
@ -56,6 +57,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@ -68,13 +70,9 @@ import static org.mockito.Mockito.when;
public class ServerCapabilityStatementProviderR5Test {
private static FhirContext ourCtx;
private final FhirContext myCtx = FhirContext.forCached(FhirVersionEnum.R5);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProviderR5Test.class);
static {
ourCtx = FhirContext.forR5();
}
private HttpServletRequest createHttpServletRequest() {
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getRequestURI()).thenReturn("/FhirStorm/fhir/Patient/_search");
@ -106,16 +104,16 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testConditionalOperations() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ConditionalProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatementRestResourceComponent res = conformance.getRest().get(0).getResource().get(1);
@ -135,24 +133,24 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testExtendedOperationReturningBundle() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
assertEquals(1, conformance.getRest().get(0).getOperation().size());
assertEquals("everything", conformance.getRest().get(0).getOperation().get(0).getName());
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
validate(opDef);
assertEquals("everything", opDef.getCode());
assertThat(opDef.getSystem(), is(false));
@ -163,19 +161,19 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testExtendedOperationReturningBundleOperation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider() {
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
};
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
validate(opDef);
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
ourLog.info(conf);
assertEquals("everything", opDef.getCode());
@ -185,32 +183,32 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testInstanceHistorySupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new InstanceHistoryProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, containsString("<interaction><code value=\"" + TypeRestfulInteraction.HISTORYINSTANCE.toCode() + "\"/></interaction>"));
}
@Test
public void testFormatIncludesSpecialNonMediaTypeFormats() throws ServletException {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SearchProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement serverConformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement serverConformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
List<String> formatCodes = serverConformance.getFormat().stream().map(c -> c.getCode()).collect(Collectors.toList());;
@ -224,10 +222,10 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testMultiOptionalDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new MultiOptionalProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
@ -245,8 +243,8 @@ public class ServerCapabilityStatementProviderR5Test {
}
assertTrue(found);
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
assertThat(conf, containsString("<documentation value=\"The patient's identifier\"/>"));
@ -257,16 +255,16 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testNonConditionalOperations() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new NonConditionalProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatementRestResourceComponent res = conformance.getRest().get(0).getResource().get(1);
@ -280,18 +278,18 @@ public class ServerCapabilityStatementProviderR5Test {
/** See #379 */
@Test
public void testOperationAcrossMultipleTypes() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new MultiTypePatientProvider(), new MultiTypeEncounterProvider());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
assertEquals(4, conformance.getRest().get(0).getOperation().size());
@ -302,9 +300,9 @@ public class ServerCapabilityStatementProviderR5Test {
assertThat(operationIdParts, containsInAnyOrder("Patient-i-someOp", "Encounter-i-someOp", "Patient-i-validate", "Encounter-i-validate"));
{
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-someOp"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-someOp"), createRequestDetails(rs));
validate(opDef);
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
Set<String> types = toStrings(opDef.getResource());
assertEquals("someOp", opDef.getCode());
assertEquals(true, opDef.getInstance());
@ -317,9 +315,9 @@ public class ServerCapabilityStatementProviderR5Test {
assertEquals("Patient", opDef.getParameter().get(1).getType().toCode());
}
{
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Encounter-i-someOp"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Encounter-i-someOp"), createRequestDetails(rs));
validate(opDef);
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
Set<String> types = toStrings(opDef.getResource());
assertEquals("someOp", opDef.getCode());
assertEquals(true, opDef.getInstance());
@ -332,9 +330,9 @@ public class ServerCapabilityStatementProviderR5Test {
assertEquals("Encounter", opDef.getParameter().get(1).getType().toCode());
}
{
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-validate"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-validate"), createRequestDetails(rs));
validate(opDef);
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
Set<String> types = toStrings(opDef.getResource());
assertEquals("validate", opDef.getCode());
assertEquals(true, opDef.getInstance());
@ -349,17 +347,17 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testOperationDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SearchProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
assertThat(conf, containsString("<documentation value=\"The patient's identifier (MRN or other card number)\"/>"));
assertThat(conf, containsString("<type value=\"token\"/>"));
@ -368,20 +366,20 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testOperationOnNoTypes() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new PlainProviderWithExtendedOperationOnNoType());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider() {
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
@Override
public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
return super.getServerConformance(theRequest, createRequestDetails(rs));
return (CapabilityStatement) super.getServerConformance(theRequest, createRequestDetails(rs));
}
};
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/-is-plain"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/-is-plain"), createRequestDetails(rs));
validate(opDef);
assertEquals("plain", opDef.getCode());
@ -408,24 +406,23 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testProviderWithRequiredAndOptional() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ProviderWithRequiredAndOptional());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatementRestComponent rest = conformance.getRest().get(0);
CapabilityStatementRestResourceComponent res = rest.getResource().get(0);
assertEquals("DiagnosticReport", res.getType());
assertEquals(DiagnosticReport.SP_SUBJECT, res.getSearchParam().get(0).getName());
// assertEquals("identifier", res.getSearchParam().get(0).getChain().get(0).getValue());
assertEquals("subject.identifier", res.getSearchParam().get(0).getName());
assertEquals(DiagnosticReport.SP_CODE, res.getSearchParam().get(1).getName());
@ -438,19 +435,19 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testReadAndVReadSupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new VreadProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, containsString("<interaction><code value=\"vread\"/></interaction>"));
assertThat(conf, containsString("<interaction><code value=\"read\"/></interaction>"));
}
@ -458,19 +455,19 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testReadSupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ReadProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, not(containsString("<interaction><code value=\"vread\"/></interaction>")));
assertThat(conf, containsString("<interaction><code value=\"read\"/></interaction>"));
}
@ -478,10 +475,10 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testSearchParameterDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SearchProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
@ -502,9 +499,9 @@ public class ServerCapabilityStatementProviderR5Test {
}
}
assertTrue(found);
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
assertThat(conf, containsString("<documentation value=\"The patient's identifier (MRN or other card number)\"/>"));
@ -518,10 +515,10 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testSearchReferenceParameterDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new PatientResourceProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
@ -538,9 +535,9 @@ public class ServerCapabilityStatementProviderR5Test {
}
}
assertTrue(found);
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
}
@ -551,10 +548,10 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testSearchReferenceParameterWithWhitelistDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SearchProviderWithWhitelist());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
@ -571,9 +568,9 @@ public class ServerCapabilityStatementProviderR5Test {
}
}
assertTrue(found);
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatementRestResourceComponent resource = findRestResource(conformance, "Patient");
@ -587,7 +584,7 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testSearchReferenceParameterWithList() throws Exception {
RestfulServer rsNoType = new RestfulServer(ourCtx){
RestfulServer rsNoType = new RestfulServer(myCtx){
@Override
public RestfulServerConfiguration createConfiguration() {
RestfulServerConfiguration retVal = super.createConfiguration();
@ -596,15 +593,15 @@ public class ServerCapabilityStatementProviderR5Test {
}
};
rsNoType.registerProvider(new SearchProviderWithListNoType());
ServerCapabilityStatementProvider scNoType = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider scNoType = new ServerCapabilityStatementProvider(rsNoType);
rsNoType.setServerConformanceProvider(scNoType);
rsNoType.init(createServletConfig());
CapabilityStatement conformance = scNoType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsNoType));
String confNoType = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) scNoType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsNoType));
String confNoType = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(confNoType);
RestfulServer rsWithType = new RestfulServer(ourCtx){
RestfulServer rsWithType = new RestfulServer(myCtx){
@Override
public RestfulServerConfiguration createConfiguration() {
RestfulServerConfiguration retVal = super.createConfiguration();
@ -613,12 +610,12 @@ public class ServerCapabilityStatementProviderR5Test {
}
};
rsWithType.registerProvider(new SearchProviderWithListWithType());
ServerCapabilityStatementProvider scWithType = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider scWithType = new ServerCapabilityStatementProvider(rsWithType);
rsWithType.setServerConformanceProvider(scWithType);
rsWithType.init(createServletConfig());
CapabilityStatement conformanceWithType = scWithType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsWithType));
String confWithType = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformanceWithType);
CapabilityStatement conformanceWithType = (CapabilityStatement) scWithType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsWithType));
String confWithType = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformanceWithType);
ourLog.info(confWithType);
assertEquals(confNoType, confWithType);
@ -628,38 +625,38 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testSystemHistorySupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SystemHistoryProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, containsString("<interaction><code value=\"" + SystemRestfulInteraction.HISTORYSYSTEM.toCode() + "\"/></interaction>"));
}
@Test
public void testTypeHistorySupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new TypeHistoryProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, containsString("<interaction><code value=\"" + TypeRestfulInteraction.HISTORYTYPE.toCode() + "\"/></interaction>"));
}
@ -667,34 +664,34 @@ public class ServerCapabilityStatementProviderR5Test {
@Disabled
public void testValidateGeneratedStatement() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new MultiOptionalProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
ValidationResult result = ourCtx.newValidator().validateWithResult(conformance);
ValidationResult result = myCtx.newValidator().validateWithResult(conformance);
assertTrue(result.isSuccessful(), result.getMessages().toString());
}
@Test
public void testSystemLevelNamedQueryWithParameters() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new NamedQueryPlainProvider());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatementRestComponent restComponent = conformance.getRest().get(0);
CapabilityStatementRestResourceOperationComponent operationComponent = restComponent.getOperation().get(0);
@ -703,11 +700,11 @@ public class ServerCapabilityStatementProviderR5Test {
String operationReference = operationComponent.getDefinition();
assertThat(operationReference, not(nullValue()));
OperationDefinition operationDefinition = sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
validate(operationDefinition);
assertThat(operationDefinition.getCode(), is(NamedQueryPlainProvider.QUERY_NAME));
assertThat("The operation name should be the description, if a description is set", operationDefinition.getName(), is(NamedQueryPlainProvider.DESCRIPTION));
assertThat("The operation name should be the description, if a description is set", operationDefinition.getName(), equalTo("Search_testQuery"));
assertThat(operationDefinition.getStatus(), is(PublicationStatus.ACTIVE));
assertThat(operationDefinition.getKind(), is(OperationKind.QUERY));
assertThat(operationDefinition.getDescription(), is(NamedQueryPlainProvider.DESCRIPTION));
@ -729,27 +726,27 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testResourceLevelNamedQueryWithParameters() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new NamedQueryResourceProvider());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatementRestComponent restComponent = conformance.getRest().get(0);
CapabilityStatementRestResourceOperationComponent operationComponent = restComponent.getOperation().get(0);
String operationReference = operationComponent.getDefinition();
assertThat(operationReference, not(nullValue()));
OperationDefinition operationDefinition = sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
validate(operationDefinition);
assertThat("The operation name should be the code if no description is set", operationDefinition.getName(), is(NamedQueryResourceProvider.QUERY_NAME));
assertThat("The operation name should be the code if no description is set", operationDefinition.getName(), equalTo("Search_testQuery"));
String patientResourceName = "Patient";
assertThat("A resource level search targets the resource of the provider it's defined in", operationDefinition.getResource().get(0).getValue(), is(patientResourceName));
assertThat(operationDefinition.getSystem(), is(false));
@ -767,31 +764,32 @@ public class ServerCapabilityStatementProviderR5Test {
CapabilityStatementRestResourceComponent patientResource = restComponent.getResource().stream()
.filter(r -> patientResourceName.equals(r.getType()))
.findAny().get();
.findAny()
.get();
assertThat("Named query parameters should not appear in the resource search params", patientResource.getSearchParam(), is(empty()));
}
@Test
public void testExtendedOperationAtTypeLevel() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new TypeLevelOperationProvider());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
List<CapabilityStatementRestResourceOperationComponent> operations = conformance.getRest().get(0).getOperation();
assertThat(operations.size(), is(1));
assertThat(operations.get(0).getName(), is(TypeLevelOperationProvider.OPERATION_NAME));
OperationDefinition opDef = sc.readOperationDefinition(new IdType(operations.get(0).getDefinition()), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType(operations.get(0).getDefinition()), createRequestDetails(rs));
validate(opDef);
assertEquals(TypeLevelOperationProvider.OPERATION_NAME, opDef.getCode());
assertThat(opDef.getSystem(), is(false));
@ -801,16 +799,16 @@ public class ServerCapabilityStatementProviderR5Test {
@Test
public void testProfiledResourceStructureDefinitionLinks() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setResourceProviders(new ProfiledPatientProvider(), new MultipleProfilesPatientProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
List<CapabilityStatementRestResourceComponent> resources = conformance.getRestFirstRep().getResource();
CapabilityStatementRestResourceComponent patientResource = resources.stream()
@ -844,7 +842,7 @@ public class ServerCapabilityStatementProviderR5Test {
}
private void validate(OperationDefinition theOpDef) {
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(theOpDef);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(theOpDef);
ourLog.info("Def: {}", conf);
}

View File

@ -40,19 +40,24 @@ import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class RestfulServerExtension implements BeforeEachCallback, AfterEachCallback {
private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerExtension.class);
private FhirContext myFhirContext;
private Object[] myProviders;
private List<Object> myProviders = new ArrayList<>();
private FhirVersionEnum myFhirVersion;
private Server myServer;
private RestfulServer myServlet;
private int myPort;
private CloseableHttpClient myHttpClient;
private IGenericClient myFhirClient;
private List<Consumer<RestfulServer>> myConsumers = new ArrayList<>();
/**
* Constructor
@ -60,7 +65,9 @@ public class RestfulServerExtension implements BeforeEachCallback, AfterEachCall
public RestfulServerExtension(FhirContext theFhirContext, Object... theProviders) {
Validate.notNull(theFhirContext);
myFhirContext = theFhirContext;
myProviders = theProviders;
if (theProviders != null) {
myProviders = new ArrayList<>(Arrays.asList(theProviders));
}
}
/**
@ -98,6 +105,8 @@ public class RestfulServerExtension implements BeforeEachCallback, AfterEachCall
ServletHolder servletHolder = new ServletHolder(myServlet);
servletHandler.addServletWithMapping(servletHolder, "/*");
myConsumers.forEach(t -> t.accept(myServlet));
myServer.setHandler(servletHandler);
myServer.start();
myPort = JettyUtil.getPortForStartedServer(myServer);
@ -140,4 +149,26 @@ public class RestfulServerExtension implements BeforeEachCallback, AfterEachCall
createContextIfNeeded();
startServer();
}
public RestfulServerExtension registerProvider(Object theProvider) {
if (myServlet != null) {
myServlet.registerProvider(theProvider);
} else {
myProviders.add(theProvider);
}
return this;
}
public RestfulServerExtension withServer(Consumer<RestfulServer> theConsumer) {
if (myServlet != null) {
theConsumer.accept(myServlet);
} else {
myConsumers.add(theConsumer);
}
return this;
}
public RestfulServerExtension registerInterceptor(Object theInterceptor) {
return withServer(t -> t.getInterceptorService().registerInterceptor(theInterceptor));
}
}

View File

@ -10,6 +10,7 @@ import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
/**
@ -39,6 +40,12 @@ public class BaseValidationSupportWrapper extends BaseValidationSupport {
return myWrap.fetchAllConformanceResources();
}
@Nullable
@Override
public <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
return myWrap.fetchAllNonBaseStructureDefinitions();
}
@Override
public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
return myWrap.fetchAllStructureDefinitions();

View File

@ -84,6 +84,12 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
return loadFromCache(myCache, key, t -> super.fetchAllStructureDefinitions());
}
@Override
public <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
String key = "fetchAllNonBaseStructureDefinitions";
return loadFromCache(myCache, key, t -> super.fetchAllNonBaseStructureDefinitions());
}
@Override
public <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) {
return loadFromCache(myCache, "fetchResource " + theClass + " " + theUri,

View File

@ -15,9 +15,12 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class ValidationSupportChain implements IValidationSupport {
@ -182,10 +185,19 @@ public class ValidationSupportChain implements IValidationSupport {
@Override
public List<IBaseResource> fetchAllStructureDefinitions() {
return doFetchStructureDefinitions(t->t.fetchAllStructureDefinitions());
}
@Override
public List<IBaseResource> fetchAllNonBaseStructureDefinitions() {
return doFetchStructureDefinitions(t->t.fetchAllNonBaseStructureDefinitions());
}
private List<IBaseResource> doFetchStructureDefinitions(Function<IValidationSupport, List<IBaseResource>> theFunction) {
ArrayList<IBaseResource> retVal = new ArrayList<>();
Set<String> urls = new HashSet<>();
for (IValidationSupport nextSupport : myChain) {
List<IBaseResource> allStructureDefinitions = nextSupport.fetchAllStructureDefinitions();
List<IBaseResource> allStructureDefinitions = theFunction.apply(nextSupport);
if (allStructureDefinitions != null) {
for (IBaseResource next : allStructureDefinitions) {

View File

@ -0,0 +1,191 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Sort;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.UriAndListParam;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import java.util.Set;
// import ca.uhn.fhir.model.dstu.resource.Binary;
// import ca.uhn.fhir.model.dstu2.resource.Bundle;
// import ca.uhn.fhir.model.api.Bundle;
public class PatientResourceProvider implements IResourceProvider
{
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
@Search()
public IBundleProvider search(
javax.servlet.http.HttpServletRequest theServletRequest,
@Description(shortDefinition="The resource identity")
@OptionalParam(name="_id")
StringAndListParam theId,
@Description(shortDefinition="The resource language")
@OptionalParam(name="_language")
StringAndListParam theResourceLanguage,
@Description(shortDefinition="Search the contents of the resource's data using a fulltext search")
@OptionalParam(name=Constants.PARAM_CONTENT)
StringAndListParam theFtContent,
@Description(shortDefinition="Search the contents of the resource's narrative using a fulltext search")
@OptionalParam(name=Constants.PARAM_TEXT)
StringAndListParam theFtText,
@Description(shortDefinition="Search for resources which have the given tag")
@OptionalParam(name=Constants.PARAM_TAG)
TokenAndListParam theSearchForTag,
@Description(shortDefinition="Search for resources which have the given security labels")
@OptionalParam(name=Constants.PARAM_SECURITY)
TokenAndListParam theSearchForSecurity,
@Description(shortDefinition="Search for resources which have the given profile")
@OptionalParam(name=Constants.PARAM_PROFILE)
UriAndListParam theSearchForProfile,
@Description(shortDefinition="A patient identifier")
@OptionalParam(name="identifier")
TokenAndListParam theIdentifier,
@Description(shortDefinition="A portion of either family or given name of the patient")
@OptionalParam(name="name")
StringAndListParam theName,
@Description(shortDefinition="A portion of the family name of the patient")
@OptionalParam(name="family")
StringAndListParam theFamily,
@Description(shortDefinition="A portion of the given name of the patient")
@OptionalParam(name="given")
StringAndListParam theGiven,
@Description(shortDefinition="A portion of either family or given name using some kind of phonetic matching algorithm")
@OptionalParam(name="phonetic")
StringAndListParam thePhonetic,
@Description(shortDefinition="The value in any kind of telecom details of the patient")
@OptionalParam(name="telecom")
TokenAndListParam theTelecom,
@Description(shortDefinition="A value in a phone contact")
@OptionalParam(name="phone")
TokenAndListParam thePhone,
@Description(shortDefinition="A value in an email contact")
@OptionalParam(name="email")
TokenAndListParam theEmail,
@Description(shortDefinition="An address in any kind of address/part of the patient")
@OptionalParam(name="address")
StringAndListParam theAddress,
@Description(shortDefinition="A city specified in an address")
@OptionalParam(name="address-city")
StringAndListParam theAddress_city,
@Description(shortDefinition="A state specified in an address")
@OptionalParam(name="address-state")
StringAndListParam theAddress_state,
@Description(shortDefinition="A postalCode specified in an address")
@OptionalParam(name="address-postalcode")
StringAndListParam theAddress_postalcode,
@Description(shortDefinition="A country specified in an address")
@OptionalParam(name="address-country")
StringAndListParam theAddress_country,
@Description(shortDefinition="A use code specified in an address")
@OptionalParam(name="address-use")
TokenAndListParam theAddress_use,
@Description(shortDefinition="Gender of the patient")
@OptionalParam(name="gender")
TokenAndListParam theGender,
@Description(shortDefinition="Language code (irrespective of use value)")
@OptionalParam(name="language")
TokenAndListParam theLanguage,
@Description(shortDefinition="The patient's date of birth")
@OptionalParam(name="birthdate")
DateRangeParam theBirthdate,
@Description(shortDefinition="The organization at which this person is a patient")
@OptionalParam(name="organization", targetTypes={ Organization.class } )
ReferenceAndListParam theOrganization,
@Description(shortDefinition="Patient's nominated care provider, could be a care manager, not the organization that manages the record")
@OptionalParam(name="careprovider", targetTypes={ Organization.class , Practitioner.class } )
ReferenceAndListParam theCareprovider,
@Description(shortDefinition="Whether the patient record is active")
@OptionalParam(name="active")
TokenAndListParam theActive,
@Description(shortDefinition="The species for animal patients")
@OptionalParam(name="animal-species")
TokenAndListParam theAnimal_species,
@Description(shortDefinition="The breed for animal patients")
@OptionalParam(name="animal-breed")
TokenAndListParam theAnimal_breed,
@Description(shortDefinition="All patients linked to the given patient")
@OptionalParam(name="link", targetTypes={ Patient.class } )
ReferenceAndListParam theLink,
@Description(shortDefinition="This patient has been marked as deceased, or as a death date entered")
@OptionalParam(name="deceased")
TokenAndListParam theDeceased,
@Description(shortDefinition="The date of death has been provided and satisfies this search value")
@OptionalParam(name="deathdate")
DateRangeParam theDeathdate,
@IncludeParam(reverse=true)
Set<Include> theRevIncludes,
@Description(shortDefinition="Only return resources which were last updated as specified by the given range")
@OptionalParam(name="_lastUpdated")
DateRangeParam theLastUpdated,
@IncludeParam(allow= {
"Patient:careprovider" , "Patient:link" , "Patient:organization" , "Patient:careprovider" , "Patient:link" , "Patient:organization" , "Patient:careprovider" , "Patient:link" , "Patient:organization" , "*"
})
Set<Include> theIncludes,
@Sort
SortSpec theSort,
@Count
Integer theCount
) {
return null;
}
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
@ -36,14 +37,14 @@ import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.IParameter;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.method.SearchParameter;
import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import com.google.common.collect.Lists;
import org.eclipse.jetty.server.Server;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestComponent;
@ -63,9 +64,11 @@ import org.hl7.fhir.r4.model.OperationDefinition;
import org.hl7.fhir.r4.model.OperationDefinition.OperationDefinitionParameterComponent;
import org.hl7.fhir.r4.model.OperationDefinition.OperationKind;
import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.servlet.ServletConfig;
@ -99,14 +102,13 @@ public class ServerCapabilityStatementProviderR4Test {
public static final String PATIENT_SUB_SUB_2 = "PatientSubSub2";
public static final String PATIENT_TRIPLE_SUB = "PatientTripleSub";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProviderR4Test.class);
private static FhirContext ourCtx;
private static FhirValidator ourValidator;
private final FhirContext myCtx = FhirContext.forCached(FhirVersionEnum.R4);
private FhirValidator myValidator;
static {
ourCtx = FhirContext.forR4();
ourValidator = ourCtx.newValidator();
ourValidator.setValidateAgainstStandardSchema(true);
ourValidator.setValidateAgainstStandardSchematron(true);
@BeforeEach
public void before() {
myValidator = myCtx.newValidator();
myValidator.registerValidatorModule(new FhirInstanceValidator(myCtx));
}
private HttpServletRequest createHttpServletRequest() {
@ -140,18 +142,19 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testConditionalOperations() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ConditionalProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
assertEquals(2, conformance.getRest().get(0).getResource().size());
CapabilityStatementRestResourceComponent res = conformance.getRest().get(0).getResource().get(1);
assertEquals("Patient", res.getType());
@ -170,24 +173,22 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testExtendedOperationReturningBundle() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
validate(conformance);
assertEquals(1, conformance.getRest().get(0).getOperation().size());
assertEquals("everything", conformance.getRest().get(0).getOperation().get(0).getName());
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
validate(opDef);
assertEquals("everything", opDef.getCode());
assertThat(opDef.getSystem(), is(false));
@ -198,19 +199,19 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testExtendedOperationReturningBundleOperation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider() {
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
};
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
validate(opDef);
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
ourLog.info(conf);
assertEquals("everything", opDef.getCode());
@ -220,29 +221,28 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testInstanceHistorySupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new InstanceHistoryProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = validate(conformance);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, containsString("<interaction><code value=\"" + TypeRestfulInteraction.HISTORYINSTANCE.toCode() + "\"/></interaction>"));
}
@Test
public void testMultiOptionalDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new MultiOptionalProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
@ -260,9 +260,8 @@ public class ServerCapabilityStatementProviderR4Test {
}
assertTrue(found);
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = validate(conformance);
assertThat(conf, containsString("<documentation value=\"The patient's identifier\"/>"));
assertThat(conf, containsString("<documentation value=\"The patient's name\"/>"));
@ -272,17 +271,16 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testNonConditionalOperations() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new NonConditionalProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
validate(conformance);
CapabilityStatementRestResourceComponent res = conformance.getRest().get(0).getResource().get(1);
assertEquals("Patient", res.getType());
@ -297,19 +295,18 @@ public class ServerCapabilityStatementProviderR4Test {
*/
@Test
public void testOperationAcrossMultipleTypes() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new MultiTypePatientProvider(), new MultiTypeEncounterProvider());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
validate(conformance);
assertEquals(4, conformance.getRest().get(0).getOperation().size());
List<String> operationNames = toOperationNames(conformance.getRest().get(0).getOperation());
@ -319,9 +316,9 @@ public class ServerCapabilityStatementProviderR4Test {
assertThat(operationIdParts, containsInAnyOrder("Patient-i-someOp", "Encounter-i-someOp", "Patient-i-validate", "Encounter-i-validate"));
{
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-someOp"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-someOp"), createRequestDetails(rs));
validate(opDef);
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
Set<String> types = toStrings(opDef.getResource());
assertEquals("someOp", opDef.getCode());
assertEquals(true, opDef.getInstance());
@ -334,9 +331,9 @@ public class ServerCapabilityStatementProviderR4Test {
assertEquals("Patient", opDef.getParameter().get(1).getType());
}
{
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Encounter-i-someOp"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Encounter-i-someOp"), createRequestDetails(rs));
validate(opDef);
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
Set<String> types = toStrings(opDef.getResource());
assertEquals("someOp", opDef.getCode());
assertEquals(true, opDef.getInstance());
@ -349,9 +346,9 @@ public class ServerCapabilityStatementProviderR4Test {
assertEquals("Encounter", opDef.getParameter().get(1).getType());
}
{
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-validate"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-validate"), createRequestDetails(rs));
validate(opDef);
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
Set<String> types = toStrings(opDef.getResource());
assertEquals("validate", opDef.getCode());
assertEquals(true, opDef.getInstance());
@ -366,17 +363,17 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testOperationDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SearchProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
String conf = validate(conformance);
assertThat(conf, containsString("<documentation value=\"The patient's identifier (MRN or other card number)\"/>"));
assertThat(conf, containsString("<type value=\"token\"/>"));
@ -385,20 +382,20 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testOperationOnNoTypes() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new PlainProviderWithExtendedOperationOnNoType());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider() {
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
@Override
public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
return super.getServerConformance(theRequest, createRequestDetails(rs));
return (CapabilityStatement) super.getServerConformance(theRequest, createRequestDetails(rs));
}
};
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/-is-plain"), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/-is-plain"), createRequestDetails(rs));
validate(opDef);
assertEquals("plain", opDef.getCode());
@ -425,23 +422,22 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testProviderWithRequiredAndOptional() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ProviderWithRequiredAndOptional());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
validate(conformance);
CapabilityStatementRestComponent rest = conformance.getRest().get(0);
CapabilityStatementRestResourceComponent res = rest.getResource().get(0);
assertEquals("DiagnosticReport", res.getType());
assertEquals(DiagnosticReport.SP_SUBJECT, res.getSearchParam().get(0).getName());
assertEquals("subject.identifier", res.getSearchParam().get(0).getName());
// assertEquals("identifier", res.getSearchParam().get(0).getChain().get(0).getValue());
assertEquals(DiagnosticReport.SP_CODE, res.getSearchParam().get(1).getName());
@ -455,19 +451,17 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testReadAndVReadSupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new VreadProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = validate(conformance);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, containsString("<interaction><code value=\"vread\"/></interaction>"));
assertThat(conf, containsString("<interaction><code value=\"read\"/></interaction>"));
}
@ -475,19 +469,19 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testReadSupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new ReadProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, not(containsString("<interaction><code value=\"vread\"/></interaction>")));
assertThat(conf, containsString("<interaction><code value=\"read\"/></interaction>"));
}
@ -495,10 +489,10 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testSearchParameterDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SearchProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
@ -519,10 +513,9 @@ public class ServerCapabilityStatementProviderR4Test {
}
}
assertTrue(found);
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
String conf = validate(conformance);
assertThat(conf, containsString("<documentation value=\"The patient's identifier (MRN or other card number)\"/>"));
assertThat(conf, containsString("<type value=\"token\"/>"));
@ -532,14 +525,14 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testFormatIncludesSpecialNonMediaTypeFormats() throws ServletException {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SearchProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement serverConformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement serverConformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
List<String> formatCodes = serverConformance.getFormat().stream().map(c -> c.getCode()).collect(Collectors.toList());
@ -555,10 +548,10 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testSearchReferenceParameterDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new PatientResourceProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
@ -575,10 +568,9 @@ public class ServerCapabilityStatementProviderR4Test {
}
}
assertTrue(found);
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
String conf = validate(conformance);
}
@ -588,10 +580,10 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testSearchReferenceParameterWithWhitelistDocumentation() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SearchProviderWithWhitelist());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
@ -608,10 +600,9 @@ public class ServerCapabilityStatementProviderR4Test {
}
}
assertTrue(found);
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
String conf = validate(conformance);
CapabilityStatementRestResourceComponent resource = findRestResource(conformance, "Patient");
@ -624,7 +615,7 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testSearchReferenceParameterWithList() throws Exception {
RestfulServer rsNoType = new RestfulServer(ourCtx) {
RestfulServer rsNoType = new RestfulServer(myCtx) {
@Override
public RestfulServerConfiguration createConfiguration() {
RestfulServerConfiguration retVal = super.createConfiguration();
@ -633,15 +624,14 @@ public class ServerCapabilityStatementProviderR4Test {
}
};
rsNoType.registerProvider(new SearchProviderWithListNoType());
ServerCapabilityStatementProvider scNoType = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider scNoType = new ServerCapabilityStatementProvider(rsNoType);
rsNoType.setServerConformanceProvider(scNoType);
rsNoType.init(createServletConfig());
CapabilityStatement conformance = scNoType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsNoType));
String confNoType = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(confNoType);
CapabilityStatement conformance = (CapabilityStatement) scNoType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsNoType));
String confNoType = validate(conformance);
RestfulServer rsWithType = new RestfulServer(ourCtx) {
RestfulServer rsWithType = new RestfulServer(myCtx) {
@Override
public RestfulServerConfiguration createConfiguration() {
RestfulServerConfiguration retVal = super.createConfiguration();
@ -650,13 +640,12 @@ public class ServerCapabilityStatementProviderR4Test {
}
};
rsWithType.registerProvider(new SearchProviderWithListWithType());
ServerCapabilityStatementProvider scWithType = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider scWithType = new ServerCapabilityStatementProvider(rsWithType);
rsWithType.setServerConformanceProvider(scWithType);
rsWithType.init(createServletConfig());
CapabilityStatement conformanceWithType = scWithType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsWithType));
String confWithType = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformanceWithType);
ourLog.info(confWithType);
CapabilityStatement conformanceWithType = (CapabilityStatement) scWithType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsWithType));
String confWithType = validate(conformanceWithType);
assertEquals(confNoType, confWithType);
assertThat(confNoType, containsString("<date value=\"2011-02-22T11:22:33Z\"/>"));
@ -665,38 +654,34 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testSystemHistorySupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new SystemHistoryProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = validate(conformance);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, containsString("<interaction><code value=\"" + SystemRestfulInteraction.HISTORYSYSTEM.toCode() + "\"/></interaction>"));
}
@Test
public void testTypeHistorySupported() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new TypeHistoryProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = validate(conformance);
conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
assertThat(conf, containsString("<interaction><code value=\"" + TypeRestfulInteraction.HISTORYTYPE.toCode() + "\"/></interaction>"));
}
@ -720,39 +705,38 @@ public class ServerCapabilityStatementProviderR4Test {
}
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new MyProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider() {
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
};
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement opDef = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement opDef = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
ourLog.info(conf);
validate(opDef);
CapabilityStatementRestResourceComponent resource = opDef.getRest().get(0).getResource().get(0);
assertEquals("DiagnosticReport", resource.getType());
List<String> searchParamNames = resource.getSearchParam().stream().map(t -> t.getName()).collect(Collectors.toList());
assertThat(searchParamNames, containsInAnyOrder("patient", "date"));
assertThat(searchParamNames, containsInAnyOrder("patient.birthdate", "patient.family", "patient.given", "date"));
}
@Test
public void testSystemLevelNamedQueryWithParameters() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new NamedQueryPlainProvider());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
validate(conformance);
CapabilityStatementRestComponent restComponent = conformance.getRest().get(0);
CapabilityStatementRestResourceOperationComponent operationComponent = restComponent.getOperation().get(0);
@ -761,11 +745,11 @@ public class ServerCapabilityStatementProviderR4Test {
String operationReference = operationComponent.getDefinition();
assertThat(operationReference, not(nullValue()));
OperationDefinition operationDefinition = sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
validate(operationDefinition);
assertThat(operationDefinition.getCode(), is(NamedQueryPlainProvider.QUERY_NAME));
assertThat("The operation name should be the description, if a description is set", operationDefinition.getName(), is(NamedQueryPlainProvider.DESCRIPTION));
assertThat(operationDefinition.getName(), is("Search_" + NamedQueryPlainProvider.QUERY_NAME));
assertThat(operationDefinition.getStatus(), is(PublicationStatus.ACTIVE));
assertThat(operationDefinition.getKind(), is(OperationKind.QUERY));
assertThat(operationDefinition.getDescription(), is(NamedQueryPlainProvider.DESCRIPTION));
@ -787,27 +771,27 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testResourceLevelNamedQueryWithParameters() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new NamedQueryResourceProvider());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
validate(conformance);
CapabilityStatementRestComponent restComponent = conformance.getRest().get(0);
CapabilityStatementRestResourceOperationComponent operationComponent = restComponent.getOperation().get(0);
String operationReference = operationComponent.getDefinition();
assertThat(operationReference, not(nullValue()));
OperationDefinition operationDefinition = sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
validate(operationDefinition);
assertThat("The operation name should be the code if no description is set", operationDefinition.getName(), is(NamedQueryResourceProvider.QUERY_NAME));
assertThat("The operation name should be the code if no description is set", operationDefinition.getName(), is("Search_" + NamedQueryResourceProvider.QUERY_NAME));
String patientResourceName = "Patient";
assertThat("A resource level search targets the resource of the provider it's defined in", operationDefinition.getResource().get(0).getValue(), is(patientResourceName));
assertThat(operationDefinition.getSystem(), is(false));
@ -831,25 +815,24 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testExtendedOperationAtTypeLevel() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setProviders(new TypeLevelOperationProvider());
rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
validate(conformance);
List<CapabilityStatementRestResourceOperationComponent> operations = conformance.getRest().get(0).getOperation();
assertThat(operations.size(), is(1));
assertThat(operations.get(0).getName(), is(TypeLevelOperationProvider.OPERATION_NAME));
OperationDefinition opDef = sc.readOperationDefinition(new IdType(operations.get(0).getDefinition()), createRequestDetails(rs));
OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType(operations.get(0).getDefinition()), createRequestDetails(rs));
validate(opDef);
assertEquals(TypeLevelOperationProvider.OPERATION_NAME, opDef.getCode());
assertThat(opDef.getSystem(), is(false));
@ -859,16 +842,16 @@ public class ServerCapabilityStatementProviderR4Test {
@Test
public void testProfiledResourceStructureDefinitionLinks() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
RestfulServer rs = new RestfulServer(myCtx);
rs.setResourceProviders(new ProfiledPatientProvider(), new MultipleProfilesPatientProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider();
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
List<CapabilityStatementRestResourceComponent> resources = conformance.getRestFirstRep().getResource();
CapabilityStatementRestResourceComponent patientResource = resources.stream()
@ -901,15 +884,24 @@ public class ServerCapabilityStatementProviderR4Test {
return retVal;
}
private void validate(OperationDefinition theOpDef) {
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(theOpDef);
ourLog.info("Def: {}", conf);
private String validate(IBaseResource theResource) {
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(theResource);
ourLog.info("Def:\n{}", conf);
ValidationResult result = ourValidator.validateWithResult(theOpDef);
String outcome = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
ValidationResult result = myValidator.validateWithResult(conf);
OperationOutcome operationOutcome = (OperationOutcome) result.toOperationOutcome();
String outcome = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome);
ourLog.info("Outcome: {}", outcome);
assertTrue(result.isSuccessful(), outcome);
List<OperationOutcome.OperationOutcomeIssueComponent> warningsAndErrors = operationOutcome
.getIssue()
.stream()
.filter(t -> t.getSeverity().ordinal() <= OperationOutcome.IssueSeverity.WARNING.ordinal()) // <= because this enum has a strange order
.collect(Collectors.toList());
assertThat(outcome, warningsAndErrors, is(empty()));
return myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(theResource);
}
@SuppressWarnings("unused")

View File

@ -1563,7 +1563,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.18</version>
<version>42.2.19</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>