Merge remote-tracking branch 'origin/master' into mdm-expansion-interceptor

This commit is contained in:
Tadgh 2021-03-31 09:47:57 -04:00
commit 74be6761b1
135 changed files with 6413 additions and 2500 deletions

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

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

@ -31,6 +31,6 @@ import com.google.common.annotations.VisibleForTesting;
@VisibleForTesting
public interface IAnonymousInterceptor {
void invoke(Pointcut thePointcut, HookParams theArgs);
void invoke(IPointcut thePointcut, HookParams theArgs);
}

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

@ -3,14 +3,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath>
</parent>

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

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

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

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
@ -78,13 +78,13 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-subscription</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -101,7 +101,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-testpage-overlay</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<classifier>classes</classifier>
</dependency>
<dependency>

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,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

@ -0,0 +1,4 @@
---
type: change
issue: 2513
title: "PointCutLatch now works with IPointcut instead of Pointcut."

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

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

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

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

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

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

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

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.dao.mdm;
/*-
* #%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 org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;

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;
@ -200,6 +203,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
@ -595,13 +600,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

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
import ca.uhn.fhir.interceptor.api.IPointcut;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.TestR4Config;
@ -424,7 +425,7 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest {
}
@Override
public void invoke(Pointcut thePointcut, HookParams theArgs) {
public void invoke(IPointcut thePointcut, HookParams theArgs) {
myHitCount.incrementAndGet();
IPreResourceAccessDetails accessDetails = theArgs.get(IPreResourceAccessDetails.class);
@ -452,7 +453,7 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest {
@Override
public void invoke(Pointcut thePointcut, HookParams theArgs) {
public void invoke(IPointcut thePointcut, HookParams theArgs) {
super.invoke(thePointcut, theArgs);
IPreResourceAccessDetails accessDetails = theArgs.get(IPreResourceAccessDetails.class);

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

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
@ -144,13 +144,13 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-test-utilities</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-test-utilities</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
@ -55,13 +55,13 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-test-utilities</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-test-utilities</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
@ -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

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

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

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -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>
@ -169,7 +164,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-converter</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
</dependency>
</dependencies>

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

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.mdm.rules.matcher;
/*-
* #%L
* HAPI FHIR - Master Data Management
* %%
* 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.util.ExtensionUtil;
import org.hl7.fhir.instance.model.api.IBase;

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

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";
@ -93,14 +109,14 @@ public class RestfulServerConfiguration {
return this;
}
public Map<String, Class<? extends IBaseResource>> getNameToSharedSupertype() {
return resourceNameToSharedSupertype;
}
public Map<String, Class<? extends IBaseResource>> getNameToSharedSupertype() {
return resourceNameToSharedSupertype;
}
public RestfulServerConfiguration setNameToSharedSupertype(Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype) {
this.resourceNameToSharedSupertype = resourceNameToSharedSupertype;
return this;
}
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

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0-PRE2-SNAPSHOT</version>
<version>5.4.0-PRE3-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
import ca.uhn.fhir.interceptor.api.IPointcut;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam;
@ -148,7 +149,7 @@ public class ServerExceptionDstu3Test {
ourServlet.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED, new IAnonymousInterceptor() {
@Override
public void invoke(Pointcut thePointcut, HookParams theArgs) {
public void invoke(IPointcut thePointcut, HookParams theArgs) {
throw new NullPointerException("Hello");
}
});

Some files were not shown because too many files have changed in this diff Show More