Merge remote-tracking branch 'origin/master' into mdm-expansion-interceptor
This commit is contained in:
commit
74be6761b1
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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."
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2506
|
||||
title: "The server generated CapabilityStatment will now include supported Profile declarations
|
||||
for FHIR R4+."
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
type: change
|
||||
issue: 2513
|
||||
title: "PointCutLatch now works with IPointcut instead of Pointcut."
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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"})
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("*");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -21,28 +21,44 @@ package ca.uhn.fhir.rest.server;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.OperationMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.SearchParameter;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
|
||||
import ca.uhn.fhir.util.VersionUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import java.util.stream.Collectors;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
public class RestfulServerConfiguration {
|
||||
public class RestfulServerConfiguration implements ISearchParamRetriever {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerConfiguration.class);
|
||||
private Collection<ResourceBinding> resourceBindings;
|
||||
private List<BaseMethodBinding<?>> serverBindings;
|
||||
private Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype;
|
||||
private Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype;
|
||||
private String implementationDescription;
|
||||
private String serverVersion = VersionUtil.getVersion();
|
||||
private String serverName = "HAPI FHIR";
|
||||
|
@ -92,15 +108,15 @@ public class RestfulServerConfiguration {
|
|||
this.serverBindings = theServerBindings;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Class<? extends IBaseResource>> getNameToSharedSupertype() {
|
||||
return resourceNameToSharedSupertype;
|
||||
}
|
||||
|
||||
public RestfulServerConfiguration setNameToSharedSupertype(Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype) {
|
||||
this.resourceNameToSharedSupertype = resourceNameToSharedSupertype;
|
||||
return this;
|
||||
}
|
||||
public Map<String, Class<? extends IBaseResource>> getNameToSharedSupertype() {
|
||||
return resourceNameToSharedSupertype;
|
||||
}
|
||||
|
||||
public RestfulServerConfiguration setNameToSharedSupertype(Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype) {
|
||||
this.resourceNameToSharedSupertype = resourceNameToSharedSupertype;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the implementationDescription
|
||||
|
@ -343,4 +359,94 @@ public class RestfulServerConfiguration {
|
|||
return retVal.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
|
||||
return getActiveSearchParams(theResourceName).get(theParamName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
|
||||
|
||||
Map<String, RuntimeSearchParam> retVal = new LinkedHashMap<>();
|
||||
|
||||
collectMethodBindings()
|
||||
.getOrDefault(theResourceName, Collections.emptyList())
|
||||
.stream()
|
||||
.filter(t -> t.getResourceName().equals(theResourceName))
|
||||
.filter(t -> t instanceof SearchMethodBinding)
|
||||
.map(t -> (SearchMethodBinding) t)
|
||||
.filter(t -> t.getQueryName() == null)
|
||||
.forEach(t -> createRuntimeBinding(retVal, t));
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void createRuntimeBinding(Map<String, RuntimeSearchParam> theMapToPopulate, SearchMethodBinding theSearchMethodBinding) {
|
||||
|
||||
List<SearchParameter> parameters = theSearchMethodBinding
|
||||
.getParameters()
|
||||
.stream()
|
||||
.filter(t -> t instanceof SearchParameter)
|
||||
.map(t -> (SearchParameter) t)
|
||||
.sorted(SearchParameterComparator.INSTANCE)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (SearchParameter nextParameter : parameters) {
|
||||
|
||||
String nextParamName = nextParameter.getName();
|
||||
|
||||
String nextParamUnchainedName = nextParamName;
|
||||
if (nextParamName.contains(".")) {
|
||||
nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.'));
|
||||
}
|
||||
|
||||
String nextParamDescription = nextParameter.getDescription();
|
||||
|
||||
/*
|
||||
* If the parameter has no description, default to the one from the resource
|
||||
*/
|
||||
if (StringUtils.isBlank(nextParamDescription)) {
|
||||
RuntimeResourceDefinition def = getFhirContext().getResourceDefinition(theSearchMethodBinding.getResourceName());
|
||||
RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName);
|
||||
if (paramDef != null) {
|
||||
nextParamDescription = paramDef.getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
if (theMapToPopulate.containsKey(nextParamUnchainedName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IIdType id = getFhirContext().getVersion().newIdType().setValue("SearchParameter/" + theSearchMethodBinding.getResourceName() + "-" + nextParamName);
|
||||
String uri = null;
|
||||
String description = nextParamDescription;
|
||||
String path = null;
|
||||
RestSearchParameterTypeEnum type = nextParameter.getParamType();
|
||||
List<RuntimeSearchParam> compositeOf = Collections.emptyList();
|
||||
Set<String> providesMembershipInCompartments = Collections.emptySet();
|
||||
Set<String> targets = Collections.emptySet();
|
||||
RuntimeSearchParam.RuntimeSearchParamStatusEnum status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE;
|
||||
Collection<String> base = Collections.singletonList(theSearchMethodBinding.getResourceName());
|
||||
RuntimeSearchParam param = new RuntimeSearchParam(id, uri, nextParamName, description, path, type, compositeOf, providesMembershipInCompartments, targets, status, base);
|
||||
theMapToPopulate.put(nextParamName, param);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class SearchParameterComparator implements Comparator<SearchParameter> {
|
||||
private static final SearchParameterComparator INSTANCE = new SearchParameterComparator();
|
||||
|
||||
@Override
|
||||
public int compare(SearchParameter theO1, SearchParameter theO2) {
|
||||
if (theO1.isRequired() == theO2.isRequired()) {
|
||||
return theO1.getName().compareTo(theO2.getName());
|
||||
}
|
||||
if (theO1.isRequired()) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue