3776 add https support for hapi fhir cli using tls (#3777)

* wip functional
TODO
- add tests
- look at adding TLS to other RestfulClientFactory classes (FhirwebRestfulClientFactory, JaxRsRestfulClientFactory, OkHttpRestfulClientFactory)

* move BaseRestServerHelper to Hapi-Fhir

* - add TLS support for JaxRsClient and OkHttpClient
- add tests

* -  to use resources and files

* -  ci fixes

* -  ci fixes

* -  ci fixes

* -  ci fixes

* - refactor and tests

* - ci fixes

* - tests

* - tests

* - tests / refactor

* - clean up

* - changelog

* - remove OkHttpClient HTTPS support

* - ci

* - Ken's code review changes

* - Ken's code review: method renaming

* - Ken's code review: javadoc

* - Code coverage

* - Create HapiFhirCliRestfulClientFactory to encapsulate new HTTPS functionality. Remove modifications to the FhirContext API.

* - version bump

* - fix unable to bind port errors by using Jetty functionality to assign random ports

* Revert "- version bump"

This reverts commit cfce82576a.

* - version bump

Co-authored-by: nathaniel.doef <nathaniel.doef@smilecdr.com>
This commit is contained in:
Nathan Doef 2022-08-02 09:27:02 -04:00 committed by GitHub
parent 8740c10ed9
commit e2bed96d74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
112 changed files with 3146 additions and 671 deletions

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory; import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.tls.TlsAuthentication;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.VersionUtil; import ca.uhn.fhir.util.VersionUtil;
@ -888,8 +889,8 @@ public class FhirContext {
* {@link #newRestfulClient(Class, String) non-generic clients}). * {@link #newRestfulClient(Class, String) non-generic clients}).
* *
* <p> * <p>
* Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation * Performance Note: This method performs an additional GET request to /metadata before
* without incurring any performance penalty * the desired request is performed.
* </p> * </p>
* *
* @param theServerBase The URL of the base for the restful FHIR server to connect to * @param theServerBase The URL of the base for the restful FHIR server to connect to

View File

@ -25,7 +25,7 @@ public final class Msg {
/** /**
* IMPORTANT: Please update the following comment after you add a new code * IMPORTANT: Please update the following comment after you add a new code
* Last code value: 2109 * Last code value: 2123
*/ */
private Msg() {} private Msg() {}

View File

@ -20,12 +20,12 @@ package ca.uhn.fhir.rest.client.api;
* #L% * #L%
*/ */
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum;
import java.util.List;
import java.util.Map;
public interface IRestfulClientFactory { public interface IRestfulClientFactory {
/** /**

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.tls;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2022 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%
*/
public class KeyStoreInfo extends StoreInfo {
private final char[] myKeyPass;
public KeyStoreInfo(String theFilePath, String theStorePass, String theKeyPass, String theAlias) {
super(theFilePath, theStorePass, theAlias);
this.myKeyPass = toCharArray(theKeyPass);
}
public char[] getKeyPass() {
return myKeyPass;
}
}

View File

@ -0,0 +1,51 @@
package ca.uhn.fhir.tls;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2022 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.i18n.Msg;
import java.util.Arrays;
import java.util.List;
public enum KeyStoreType {
PKCS12("p12", "pfx"),
JKS("jks");
private List<String> myFileExtensions;
KeyStoreType(String... theFileExtensions){
myFileExtensions = Arrays.asList(theFileExtensions);
}
public List<String> getFileExtensions() {
return myFileExtensions;
}
public static KeyStoreType fromFileExtension(String theFileExtension) {
for(KeyStoreType type : KeyStoreType.values()){
if(type.getFileExtensions().contains(theFileExtension.toLowerCase())){
return type;
}
}
throw new IllegalArgumentException(Msg.code(2121)+"Invalid KeyStore Type");
}
}

View File

@ -0,0 +1,37 @@
package ca.uhn.fhir.tls;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2022 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%
*/
public enum PathType {
FILE("file://"),
RESOURCE("classpath:");
private String myPrefix;
PathType(String thePrefix) {
myPrefix = thePrefix;
}
public String getPrefix(){
return myPrefix;
}
}

View File

@ -0,0 +1,66 @@
package ca.uhn.fhir.tls;
import ca.uhn.fhir.i18n.Msg;
import org.apache.commons.io.FilenameUtils;
import static org.apache.commons.lang3.StringUtils.isBlank;
public abstract class StoreInfo {
private final String myFilePath;
private final PathType myPathType;
private final char[] myStorePass;
private final String myAlias;
private final KeyStoreType myType;
public StoreInfo(String theFilePath, String theStorePass, String theAlias) {
if(theFilePath.startsWith(PathType.RESOURCE.getPrefix())){
myFilePath = theFilePath.substring(PathType.RESOURCE.getPrefix().length());
myPathType = PathType.RESOURCE;
}
else if(theFilePath.startsWith(PathType.FILE.getPrefix())){
myFilePath = theFilePath.substring(PathType.FILE.getPrefix().length());
myPathType = PathType.FILE;
}
else {
throw new StoreInfoException(Msg.code(2117)+"Invalid path prefix");
}
myStorePass = toCharArray(theStorePass);
myAlias = theAlias;
String extension = FilenameUtils.getExtension(theFilePath);
myType = KeyStoreType.fromFileExtension(extension);
}
public String getFilePath() {
return myFilePath;
}
public char[] getStorePass() {
return myStorePass;
}
public String getAlias() {
return myAlias;
}
public KeyStoreType getType() {
return myType;
}
public PathType getPathType() {
return myPathType;
}
protected char[] toCharArray(String theString){
return isBlank(theString) ? "".toCharArray() : theString.toCharArray();
}
public static class StoreInfoException extends RuntimeException {
private static final long serialVersionUID = 1l;
public StoreInfoException(String theMessage) {
super(theMessage);
}
}
}

View File

@ -0,0 +1,42 @@
package ca.uhn.fhir.tls;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2022 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.Optional;
public class TlsAuthentication {
private final Optional<KeyStoreInfo> myKeyStoreInfo;
private final Optional<TrustStoreInfo> myTrustStoreInfo;
public TlsAuthentication(Optional<KeyStoreInfo> theKeyStoreInfo, Optional<TrustStoreInfo> theTrustStoreInfo) {
myKeyStoreInfo = theKeyStoreInfo;
myTrustStoreInfo = theTrustStoreInfo;
}
public Optional<KeyStoreInfo> getKeyStoreInfo() {
return myKeyStoreInfo;
}
public Optional<TrustStoreInfo> getTrustStoreInfo() {
return myTrustStoreInfo;
}
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.tls;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2022 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%
*/
public class TrustStoreInfo extends StoreInfo{
public TrustStoreInfo(String theFilePath, String theStorePass, String theAlias) {
super(theFilePath, theStorePass, theAlias);
}
}

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

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

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -20,14 +20,21 @@ package ca.uhn.fhir.cli;
* #L% * #L%
*/ */
import ca.uhn.fhir.cli.client.HapiFhirCliRestfulClientFactory;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.impl.RestfulClientFactory;
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
import ca.uhn.fhir.tls.TlsAuthentication;
import ca.uhn.fhir.tls.KeyStoreInfo;
import ca.uhn.fhir.tls.TrustStoreInfo;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option; import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup; import org.apache.commons.cli.OptionGroup;
@ -56,6 +63,7 @@ import java.io.Console;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -65,6 +73,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@ -80,6 +89,9 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
protected static final String BASE_URL_PARAM_LONGOPT = "target"; protected static final String BASE_URL_PARAM_LONGOPT = "target";
protected static final String BASE_URL_PARAM_NAME = "target"; protected static final String BASE_URL_PARAM_NAME = "target";
protected static final String BASE_URL_PARAM_DESC = "Base URL for the target server (e.g. \"http://example.com/fhir\")."; protected static final String BASE_URL_PARAM_DESC = "Base URL for the target server (e.g. \"http://example.com/fhir\").";
protected static final String TLS_AUTH_PARAM_LONGOPT = "tls-auth";
protected static final String TLS_AUTH_PARAM_NAME = "tls-auth";
protected static final String TLS_AUTH_PARAM_DESC = "If specified, this parameter supplies a path and filename for a json authentication file that will be used to authenticate HTTPS requests.";
protected static final String BASIC_AUTH_PARAM = "b"; protected static final String BASIC_AUTH_PARAM = "b";
protected static final String BASIC_AUTH_PARAM_LONGOPT = "basic-auth"; protected static final String BASIC_AUTH_PARAM_LONGOPT = "basic-auth";
protected static final String BASIC_AUTH_PARAM_NAME = "basic-auth"; protected static final String BASIC_AUTH_PARAM_NAME = "basic-auth";
@ -118,6 +130,10 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
addOptionalOption(theOptions, null, THREAD_COUNT, "count", "If specified, this argument specifies the number of worker threads used (default is " + DEFAULT_THREAD_COUNT + ")"); addOptionalOption(theOptions, null, THREAD_COUNT, "count", "If specified, this argument specifies the number of worker threads used (default is " + DEFAULT_THREAD_COUNT + ")");
} }
protected void addHttpsAuthOption(Options theOptions){
addOptionalOption(theOptions, null, TLS_AUTH_PARAM_LONGOPT, TLS_AUTH_PARAM_NAME, TLS_AUTH_PARAM_DESC);
}
protected String promptUser(String thePrompt) throws ParseException { protected String promptUser(String thePrompt) throws ParseException {
System.out.print(ansi().bold().fgBrightDefault()); System.out.print(ansi().bold().fgBrightDefault());
@ -457,10 +473,11 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
} }
protected IGenericClient newClient(CommandLine theCommandLine) throws ParseException { protected IGenericClient newClient(CommandLine theCommandLine) throws ParseException {
return newClient(theCommandLine, BASE_URL_PARAM, BASIC_AUTH_PARAM, BEARER_TOKEN_PARAM_LONGOPT); return newClient(theCommandLine, BASE_URL_PARAM, BASIC_AUTH_PARAM, BEARER_TOKEN_PARAM_LONGOPT, TLS_AUTH_PARAM_LONGOPT);
} }
protected IGenericClient newClient(CommandLine theCommandLine, String theBaseUrlParamName, String theBasicAuthOptionName, String theBearerTokenOptionName) throws ParseException { protected IGenericClient newClient(CommandLine theCommandLine, String theBaseUrlParamName, String theBasicAuthOptionName,
String theBearerTokenOptionName, String theTlsAuthOptionName) throws ParseException {
String baseUrl = theCommandLine.getOptionValue(theBaseUrlParamName); String baseUrl = theCommandLine.getOptionValue(theBaseUrlParamName);
if (isBlank(baseUrl)) { if (isBlank(baseUrl)) {
throw new ParseException(Msg.code(1579) + "No target server (-" + BASE_URL_PARAM + ") specified."); throw new ParseException(Msg.code(1579) + "No target server (-" + BASE_URL_PARAM + ") specified.");
@ -468,10 +485,18 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
throw new ParseException(Msg.code(1580) + "Invalid target server specified, must begin with 'http' or 'file'."); throw new ParseException(Msg.code(1580) + "Invalid target server specified, must begin with 'http' or 'file'.");
} }
return newClientWithBaseUrl(theCommandLine, baseUrl, theBasicAuthOptionName, theBearerTokenOptionName); return newClientWithBaseUrl(theCommandLine, baseUrl, theBasicAuthOptionName, theBearerTokenOptionName, theTlsAuthOptionName);
} }
protected IGenericClient newClientWithBaseUrl(CommandLine theCommandLine, String theBaseUrl, String theBasicAuthOptionName, String theBearerTokenOptionName) throws ParseException { protected IGenericClient newClientWithBaseUrl(CommandLine theCommandLine, String theBaseUrl, String theBasicAuthOptionName,
String theBearerTokenOptionName, String theTlsAuthOptionName) throws ParseException {
Optional<TlsAuthentication> tlsConfig = createTlsConfig(theCommandLine, theTlsAuthOptionName);
RestfulClientFactory restfulClientFactory = tlsConfig.isPresent()
? new HapiFhirCliRestfulClientFactory(myFhirCtx, tlsConfig.get())
: new HapiFhirCliRestfulClientFactory(myFhirCtx);
myFhirCtx.setRestfulClientFactory(restfulClientFactory);
myFhirCtx.getRestfulClientFactory().setSocketTimeout((int) DateUtils.MILLIS_PER_HOUR); myFhirCtx.getRestfulClientFactory().setSocketTimeout((int) DateUtils.MILLIS_PER_HOUR);
IGenericClient retVal = myFhirCtx.newRestfulGenericClient(theBaseUrl); IGenericClient retVal = myFhirCtx.newRestfulGenericClient(theBaseUrl);
@ -490,6 +515,62 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
return retVal; return retVal;
} }
private Optional<TlsAuthentication> createTlsConfig(CommandLine theCommandLine, String theTlsAuthOptionName){
String httpAuthFilePath = theCommandLine.getOptionValue(theTlsAuthOptionName);
if(isBlank(httpAuthFilePath)){
return Optional.empty();
}
try(FileReader fileReader = new FileReader(httpAuthFilePath)) {
JsonObject json = JsonParser.parseReader(fileReader).getAsJsonObject();
Optional<KeyStoreInfo> keyStoreInfo = createKeyStoreInfo(json.get("keyStore").getAsJsonObject());
Optional<TrustStoreInfo> trustStoreInfo = createTrustStoreInfo(json.get("trustStore").getAsJsonObject());
if(keyStoreInfo.isEmpty() && trustStoreInfo.isEmpty()){
return Optional.empty();
}
return Optional.of(new TlsAuthentication(keyStoreInfo, trustStoreInfo));
}
catch(Exception e){
throw new RuntimeException(Msg.code(2115)+"Could not create TLS configuration options", e);
}
}
private Optional<KeyStoreInfo> createKeyStoreInfo(JsonObject theJson) throws ParseException {
String filePath = theJson.get("filePath").getAsString();
if(isBlank(filePath)){
return Optional.empty();
}
String storePass = theJson.get("storePass").getAsString();
if (PROMPT.equals(storePass)) {
storePass = trim(promptUser("Enter the store password for the keystore: "));
}
String keyPass = theJson.get("keyPass").getAsString();
if (PROMPT.equals(keyPass)) {
keyPass = trim(promptUser("Enter the key password for the keystore: "));
}
String alias = theJson.get("alias").getAsString();
KeyStoreInfo keyStoreInfo = new KeyStoreInfo(filePath, storePass, keyPass, alias);
return Optional.of(keyStoreInfo);
}
private Optional<TrustStoreInfo> createTrustStoreInfo(JsonObject theJson) throws ParseException {
String filePath = theJson.get("filePath").getAsString();
if(isBlank(filePath)){
return Optional.empty();
}
String storePass = theJson.get("storePass").getAsString();
if (PROMPT.equals(storePass)) {
storePass = trim(promptUser("Enter the store password for the truststore: "));
}
String alias = theJson.get("alias").getAsString();
TrustStoreInfo trustStoreInfo = new TrustStoreInfo(filePath, storePass, alias);
return Optional.of(trustStoreInfo);
}
private String getAndParseBearerTokenAuthHeader(CommandLine theCommandLine, String theBearerTokenOptionName) throws ParseException { private String getAndParseBearerTokenAuthHeader(CommandLine theCommandLine, String theBearerTokenOptionName) throws ParseException {
String value = theCommandLine.getOptionValue(theBearerTokenOptionName); String value = theCommandLine.getOptionValue(theBearerTokenOptionName);
if (PROMPT.equals(value)) { if (PROMPT.equals(value)) {

View File

@ -42,7 +42,8 @@ public abstract class BaseRequestGeneratingCommand extends BaseCommand {
BASE_URL, BASE_URL,
BASIC_AUTH, BASIC_AUTH,
VERBOSE_LOGGING, VERBOSE_LOGGING,
HEADER_PASSTHROUGH HEADER_PASSTHROUGH,
TLS_AUTH
} }
@ -82,16 +83,21 @@ public abstract class BaseRequestGeneratingCommand extends BaseCommand {
addHeaderPassthroughOption(options); addHeaderPassthroughOption(options);
} }
if (! theExcludeOptions.contains(BaseRequestGeneratingCommandOptions.TLS_AUTH)) {
addHttpsAuthOption(options);
}
return options; return options;
} }
@Override @Override
protected IGenericClient newClientWithBaseUrl(CommandLine theCommandLine, String theBaseUrl, protected IGenericClient newClientWithBaseUrl(CommandLine theCommandLine, String theBaseUrl,
String theBasicAuthOptionName, String theBearerTokenOptionName) throws ParseException { String theBasicAuthOptionName, String theBearerTokenOptionName, String theTlsAuthOptionName) throws ParseException {
IGenericClient client = super.newClientWithBaseUrl( IGenericClient client = super.newClientWithBaseUrl(
theCommandLine, theBaseUrl, theBasicAuthOptionName, theBearerTokenOptionName); theCommandLine, theBaseUrl, theBasicAuthOptionName, theBearerTokenOptionName, theTlsAuthOptionName);
registerHeaderPassthrough(theCommandLine, client); registerHeaderPassthrough(theCommandLine, client);
return client; return client;

View File

@ -129,7 +129,7 @@ public class BulkImportCommand extends BaseCommand {
String targetBaseUrl = theCommandLine.getOptionValue(TARGET_BASE); String targetBaseUrl = theCommandLine.getOptionValue(TARGET_BASE);
ourLog.info("Initiating bulk import against server: {}", targetBaseUrl); ourLog.info("Initiating bulk import against server: {}", targetBaseUrl);
IGenericClient client = newClient(theCommandLine, TARGET_BASE, BASIC_AUTH_PARAM, BEARER_TOKEN_PARAM_LONGOPT); IGenericClient client = newClient(theCommandLine, TARGET_BASE, BASIC_AUTH_PARAM, BEARER_TOKEN_PARAM_LONGOPT, TLS_AUTH_PARAM_LONGOPT);
client.registerInterceptor(new LoggingInterceptor(false)); client.registerInterceptor(new LoggingInterceptor(false));
IBaseParameters request = createRequest(sourceBaseUrl, indexes, resourceTypes); IBaseParameters request = createRequest(sourceBaseUrl, indexes, resourceTypes);

View File

@ -46,6 +46,7 @@ import java.util.concurrent.ExecutionException;
import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.defaultString;
public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConceptMapCommand { public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConceptMapCommand {
public static final String COMMAND = "export-conceptmap-to-csv";
// TODO: Don't use qualified names for loggers in HAPI CLI. // TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommand.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommand.class);

View File

@ -51,6 +51,8 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConceptMapCommand { public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConceptMapCommand {
public static final String COMMAND = "import-csv-to-conceptmap";
// TODO: Don't use qualified names for loggers in HAPI CLI. // TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommand.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommand.class);

View File

@ -0,0 +1,149 @@
package ca.uhn.fhir.cli.client;
/*
* #%L
* HAPI FHIR - Command Line Client - API
* %%
* Copyright (C) 2014 - 2022 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.i18n.Msg;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.apache.ApacheHttpClient;
import ca.uhn.fhir.rest.client.api.Header;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.impl.RestfulClientFactory;
import ca.uhn.fhir.rest.client.tls.TlsAuthenticationSvc;
import ca.uhn.fhir.tls.TlsAuthentication;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import javax.net.ssl.SSLContext;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Intended for use with the HapiFhir CLI only.
* <br/><br/>
* A Restful Factory to create clients, requests and responses based on the Apache httpclient library.
* This class supports HTTP and HTTPS protocol and attempts to use the same client whenever possible.
* The method {@link HapiFhirCliRestfulClientFactory#useHttp()} should be used if the protocol needs to be changed to HTTP.
* Similarly, the method {@link HapiFhirCliRestfulClientFactory#useHttps(TlsAuthentication)} should be used if the protocol
* needs to be changed to HTTPS or if new TLS credentials are required for a client request.
*/
public class HapiFhirCliRestfulClientFactory extends RestfulClientFactory {
private HttpClient myHttpClient;
private TlsAuthentication myTlsAuthentication;
public HapiFhirCliRestfulClientFactory(FhirContext theFhirContext) {
this(theFhirContext, null);
}
public HapiFhirCliRestfulClientFactory(FhirContext theFhirContext, TlsAuthentication theTlsAuthentication) {
super(theFhirContext);
myTlsAuthentication = theTlsAuthentication;
}
@Override
protected synchronized IHttpClient getHttpClient(String theServerBase) {
return getHttpClient(new StringBuilder(theServerBase), null, null, null, null);
}
@Override
public synchronized IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams,
String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) {
return new ApacheHttpClient(getNativeHttpClient(), theUrl, theIfNoneExistParams, theIfNoneExistString, theRequestType, theHeaders);
}
public HttpClient getNativeHttpClient() {
if (myHttpClient == null) {
RequestConfig defaultRequestConfig =
RequestConfig.custom()
.setSocketTimeout(getSocketTimeout())
.setConnectTimeout(getConnectTimeout())
.setConnectionRequestTimeout(getConnectionRequestTimeout())
.setStaleConnectionCheckEnabled(true)
.build();
HttpClientBuilder builder = HttpClients.custom()
.useSystemProperties()
.setDefaultRequestConfig(defaultRequestConfig)
.disableCookieManagement();
PoolingHttpClientConnectionManager connectionManager;
if(myTlsAuthentication != null){
SSLContext sslContext = TlsAuthenticationSvc.createSslContext(myTlsAuthentication);
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
builder.setSSLSocketFactory(sslConnectionSocketFactory);
Registry<ConnectionSocketFactory> registry = RegistryBuilder
.<ConnectionSocketFactory> create()
.register("https", sslConnectionSocketFactory)
.build();
connectionManager = new PoolingHttpClientConnectionManager(
registry, null, null, null, 5000, TimeUnit.MILLISECONDS
);
}
else {
connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
}
connectionManager.setMaxTotal(getPoolMaxTotal());
connectionManager.setDefaultMaxPerRoute(getPoolMaxPerRoute());
builder.setConnectionManager(connectionManager);
myHttpClient = builder.build();
}
return myHttpClient;
}
@Override
protected void resetHttpClient() {
myHttpClient = null;
}
public void useHttp(){
myTlsAuthentication = null;
resetHttpClient();
}
public void useHttps(TlsAuthentication theTlsAuthentication){
myTlsAuthentication = theTlsAuthentication;
resetHttpClient();
}
@Override
public synchronized void setHttpClient(Object theHttpClient) {
throw new UnsupportedOperationException(Msg.code(2119));
}
@Override
public void setProxy(String theHost, Integer thePort) {
throw new UnsupportedOperationException(Msg.code(2120));
}
}

View File

@ -0,0 +1,83 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory;
import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import javax.net.ssl.SSLHandshakeException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class ApacheRestfulClientFactoryTest extends BaseFhirVersionParameterizedTest {
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testNativeClientHttp(FhirVersionEnum theFhirVersion) throws Exception {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
ApacheRestfulClientFactory clientFactory = new ApacheRestfulClientFactory(fhirVersionParams.getFhirContext());
HttpClient client = clientFactory.getNativeHttpClient();
HttpUriRequest request = new HttpGet(fhirVersionParams.getPatientEndpoint());
HttpResponse response = client.execute(request);
assertEquals(200, response.getStatusLine().getStatusCode());
String json = EntityUtils.toString(response.getEntity());
IBaseResource bundle = fhirVersionParams.parseResource(json);
assertEquals(fhirVersionParams.getFhirVersion(), bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testNativeClientHttpsNoCredentials(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
ApacheRestfulClientFactory clientFactory = new ApacheRestfulClientFactory(fhirVersionParams.getFhirContext());
HttpClient unauthenticatedClient = clientFactory.getNativeHttpClient();
try{
HttpUriRequest request = new HttpGet(fhirVersionParams.getSecuredPatientEndpoint());
unauthenticatedClient.execute(request);
fail();
}
catch(Exception e){
assertEquals(SSLHandshakeException.class, e.getClass());
}
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testGenericClientHttp(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
String base = fhirVersionParams.getBase();
FhirContext context = fhirVersionParams.getFhirContext();
context.setRestfulClientFactory(new ApacheRestfulClientFactory(context));
IBaseResource bundle = context.newRestfulGenericClient(base).search().forResource("Patient").execute();
assertEquals(theFhirVersion, bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testGenericClientHttpsNoCredentials(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
String secureBase = fhirVersionParams.getSecureBase();
FhirContext context = fhirVersionParams.getFhirContext();
context.setRestfulClientFactory(new ApacheRestfulClientFactory(context));
try {
context.newRestfulGenericClient(secureBase).search().forResource("Patient").execute();
fail();
} catch (Exception e) {
assertTrue(e.getMessage().contains("HAPI-1357: Failed to retrieve the server metadata statement during client initialization"));
assertEquals(SSLHandshakeException.class, e.getCause().getCause().getClass());
}
}
}

View File

@ -32,25 +32,26 @@ class BaseRequestGeneratingCommandTest {
@Test @Test
void getOptions() { void getOptions() {
Options options = tested.getOptions(); Options options = tested.getOptions();
assertEquals(6, options.getOptions().size()); assertEquals(7, options.getOptions().size());
assertTrue(options.hasShortOption(BaseCommand.FHIR_VERSION_PARAM)); assertTrue(options.hasShortOption(BaseCommand.FHIR_VERSION_PARAM));
assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM)); assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM));
assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM)); assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM));
assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME)); assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME));
assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM)); assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM));
assertTrue(options.hasShortOption(BaseRequestGeneratingCommand.HEADER_PASSTHROUGH)); assertTrue(options.hasShortOption(BaseRequestGeneratingCommand.HEADER_PASSTHROUGH));
assertTrue(options.hasShortOption(BaseRequestGeneratingCommand.TLS_AUTH_PARAM_NAME));
} }
@ParameterizedTest(name = "Excluding {0}") @ParameterizedTest(name = "Excluding {0}")
@EnumSource(value = BaseRequestGeneratingCommandOptions.class, @EnumSource(value = BaseRequestGeneratingCommandOptions.class,
names = {"VERSION", "BASE_URL", "BASIC_AUTH", "VERBOSE_LOGGING", "HEADER_PASSTHROUGH"}) names = {"VERSION", "BASE_URL", "BASIC_AUTH", "VERBOSE_LOGGING", "HEADER_PASSTHROUGH", "TLS_AUTH"})
void getSomeOptionsExcludingOne(BaseRequestGeneratingCommandOptions excludedOption) { void getSomeOptionsExcludingOne(BaseRequestGeneratingCommandOptions excludedOption) {
Collection<BaseRequestGeneratingCommandOptions> excludeOptions = Collections.singleton(excludedOption); Collection<BaseRequestGeneratingCommandOptions> excludeOptions = Collections.singleton(excludedOption);
Options options = tested.getSomeOptions(excludeOptions); Options options = tested.getSomeOptions(excludeOptions);
// BASIC_AUTH exclusion excludes 2 options // BASIC_AUTH exclusion excludes 2 options
int expectedSize = excludedOption == BASIC_AUTH ? 4 : 5; int expectedSize = excludedOption == BASIC_AUTH ? 5 : 6;
assertEquals(expectedSize, options.getOptions().size()); assertEquals(expectedSize, options.getOptions().size());
assertFalse(options.hasShortOption(getOptionForExcludedOption(excludedOption))); assertFalse(options.hasShortOption(getOptionForExcludedOption(excludedOption)));
@ -86,6 +87,9 @@ class BaseRequestGeneratingCommandTest {
case HEADER_PASSTHROUGH: case HEADER_PASSTHROUGH:
return BaseRequestGeneratingCommand.HEADER_PASSTHROUGH; return BaseRequestGeneratingCommand.HEADER_PASSTHROUGH;
case TLS_AUTH:
return BaseCommand.TLS_AUTH_PARAM_NAME;
} }
throw new InvalidParameterException("unexpected exclude option " + excludeOption); throw new InvalidParameterException("unexpected exclude option " + excludeOption);
} }
@ -94,11 +98,12 @@ class BaseRequestGeneratingCommandTest {
void getSomeOptionsExcludeTwo() { void getSomeOptionsExcludeTwo() {
Options options = tested.getSomeOptions(Lists.newArrayList(VERSION, HEADER_PASSTHROUGH)); Options options = tested.getSomeOptions(Lists.newArrayList(VERSION, HEADER_PASSTHROUGH));
assertEquals(4, options.getOptions().size()); assertEquals(5, options.getOptions().size());
assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM)); assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM));
assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM)); assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM));
assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME)); assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME));
assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM)); assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM));
assertTrue(options.hasShortOption(BaseCommand.TLS_AUTH_PARAM_NAME));
} }

View File

@ -1,15 +1,19 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; import ca.uhn.fhir.test.utilities.TlsAuthenticationTestHelper;
import ca.uhn.fhir.test.utilities.RestServerR4Helper;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Resource;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
@ -24,10 +28,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class ExampleDataUploaderTest { class ExampleDataUploaderTest {
private FhirContext myCtx = FhirContext.forR4();
@RegisterExtension @RegisterExtension
public final RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(myCtx); public final RestServerR4Helper myRestServerR4Helper = new RestServerR4Helper();
@RegisterExtension
public TlsAuthenticationTestHelper myTlsAuthenticationTestHelper = new TlsAuthenticationTestHelper();
private final CapturingInterceptor myCapturingInterceptor = new CapturingInterceptor(); private final CapturingInterceptor myCapturingInterceptor = new CapturingInterceptor();
private final ExampleDataUploader testedCommand = new RequestCapturingExampleDataUploader(myCapturingInterceptor); private final ExampleDataUploader testedCommand = new RequestCapturingExampleDataUploader(myCapturingInterceptor);
@ -40,19 +44,20 @@ class ExampleDataUploaderTest {
inputFilePath = resourcesPath + "/sample.json.zip"; inputFilePath = resourcesPath + "/sample.json.zip";
} }
@ParameterizedTest
@ValueSource(booleans = {true, false})
@Test public void testHeaderPassthrough(boolean theIncludeTls) throws ParseException {
public void testHeaderPassthrough() throws ParseException {
String headerKey = "test-header-key"; String headerKey = "test-header-key";
String headerValue = "test header value"; String headerValue = "test header value";
String[] args = new String[] { String[] args = myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
"-v", "r4", // BaseRequestGeneratingCommandTest required "-v", "r4", // BaseRequestGeneratingCommandTest required
"-t", "http://localhost:8000", // BaseRequestGeneratingCommandTest required
"-d", inputFilePath, "-d", inputFilePath,
"-hp", headerKey + ":" + headerValue // optional "-hp", headerKey + ":" + headerValue // optional
}; },
"-t", theIncludeTls, myRestServerR4Helper // BaseRequestGeneratingCommandTest required
);
final CommandLine commandLine = new DefaultParser().parse(testedCommand.getOptions(), args, true); final CommandLine commandLine = new DefaultParser().parse(testedCommand.getOptions(), args, true);
testedCommand.run(commandLine); testedCommand.run(commandLine);
@ -65,8 +70,13 @@ class ExampleDataUploaderTest {
assertEquals(1, allHeaders.get(headerKey).size()); assertEquals(1, allHeaders.get(headerKey).size());
assertThat(allHeaders.get(headerKey), hasItems(headerValue)); assertThat(allHeaders.get(headerKey), hasItems(headerValue));
}
assertEquals(1, myRestServerR4Helper.getTransactions().size());
Bundle bundle = myRestServerR4Helper.getTransactions().get(0);
Resource resource = bundle.getEntry().get(0).getResource();
assertEquals(Patient.class, resource.getClass());
assertEquals("EX3152", resource.getIdElement().getIdPart());
}
private static class RequestCapturingExampleDataUploader extends ExampleDataUploader { private static class RequestCapturingExampleDataUploader extends ExampleDataUploader {
private final CapturingInterceptor myCapturingInterceptor; private final CapturingInterceptor myCapturingInterceptor;

View File

@ -1,25 +1,21 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.test.utilities.TlsAuthenticationTestHelper;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.test.utilities.RestServerDstu3Helper;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.LoggingExtension;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.ConceptMap; import org.hl7.fhir.dstu3.model.ConceptMap;
import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence; import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.dstu3.model.UriType;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -38,30 +34,50 @@ public class ExportConceptMapToCsvCommandDstu3Test {
private static final String CS_URL_3 = "http://example.com/codesystem/3"; private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILE = "./target/output_dstu3.csv"; private static final String FILE = "./target/output_dstu3.csv";
private static String ourBase; private final FhirContext myCtx = FhirContext.forDstu3();
private static IGenericClient ourClient; private final String myVersion = "dstu3";
private static FhirContext ourCtx = FhirContext.forDstu3();
private static int ourPort;
private static Server ourServer;
private static String ourVersion = "dstu3";
static { static {
System.setProperty("test", "true"); System.setProperty("test", "true");
} }
@RegisterExtension @RegisterExtension
public LoggingExtension myLoggingExtension = new LoggingExtension(); public final RestServerDstu3Helper myRestServerDstu3Helper = new RestServerDstu3Helper(true);
@RegisterExtension
public TlsAuthenticationTestHelper myTlsAuthenticationTestHelper = new TlsAuthenticationTestHelper();
@Test @BeforeEach
public void testExportConceptMapToCsvCommand() throws IOException { public void before(){
ourLog.debug("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap())); myRestServerDstu3Helper.setConceptMapResourceProvider(new HashMapResourceProviderConceptMapDstu3(myCtx));
myRestServerDstu3Helper.getClient().create().resource(createConceptMap()).execute();
}
App.main(new String[]{"export-conceptmap-to-csv", @AfterEach
"-v", ourVersion, public void afterEach() {
"-t", ourBase, myRestServerDstu3Helper.clearDataAndCounts();
}
@AfterAll
public static void afterAll(){
TestUtil.randomizeLocaleAndTimezone();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testExportConceptMapToCsvCommandNoTls(boolean theIncludeTls) throws IOException {
ourLog.debug("ConceptMap:\n" + myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
ExportConceptMapToCsvCommand.COMMAND,
"-v", myVersion,
"-u", CM_URL, "-u", CM_URL,
"-f", FILE, "-f", FILE,
"-l"}); "-l"
},
"-t", theIncludeTls, myRestServerDstu3Helper
));
await().until(() -> new File(FILE).exists()); await().until(() -> new File(FILE).exists());
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" + String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
@ -85,36 +101,6 @@ public class ExportConceptMapToCsvCommandDstu3Test {
FileUtils.deleteQuietly(new File(FILE)); FileUtils.deleteQuietly(new File(FILE));
} }
@AfterAll
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
TestUtil.randomizeLocaleAndTimezone();
}
@BeforeAll
public static void beforeClass() throws Exception {
ourServer = new Server(0);
ServletHandler servletHandler = new ServletHandler();
RestfulServer restfulServer = new RestfulServer(ourCtx);
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapDstu3(ourCtx));
ServletHolder servletHolder = new ServletHolder(restfulServer);
servletHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(servletHandler);
JettyUtil.startServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourBase = "http://localhost:" + ourPort;
ourClient = ourCtx.newRestfulGenericClient(ourBase);
ourClient.create().resource(createConceptMap()).execute();
}
static ConceptMap createConceptMap() { static ConceptMap createConceptMap() {
ConceptMap conceptMap = new ConceptMap(); ConceptMap conceptMap = new ConceptMap();
conceptMap conceptMap

View File

@ -1,23 +1,21 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.test.utilities.TlsAuthenticationTestHelper;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.test.utilities.RestServerR4Helper;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.UriType;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -36,27 +34,50 @@ public class ExportConceptMapToCsvCommandR4Test {
private static final String CS_URL_3 = "http://example.com/codesystem/3"; private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILE = new File("./target/output_r4.csv").getAbsolutePath(); private static final String FILE = new File("./target/output_r4.csv").getAbsolutePath();
private static String ourBase;
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static int ourPort;
private static Server ourServer;
private static String ourVersion = "r4";
static { static {
System.setProperty("test", "true"); System.setProperty("test", "true");
} }
@Test @RegisterExtension
public void testExportConceptMapToCsvCommand() throws IOException { public final RestServerR4Helper myRestServerR4Helper = new RestServerR4Helper(true);
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap())); @RegisterExtension
public TlsAuthenticationTestHelper myTlsAuthenticationTestHelper = new TlsAuthenticationTestHelper();
App.main(new String[]{"export-conceptmap-to-csv", private final FhirContext myCtx = FhirContext.forR4();
"-v", ourVersion, private final String myVersion = "r4";
"-t", ourBase,
@BeforeEach
public void before(){
myRestServerR4Helper.setConceptMapResourceProvider(new HashMapResourceProviderConceptMapR4(myCtx));
myRestServerR4Helper.getClient().create().resource(createConceptMap()).execute();
}
@AfterEach
public void afterEach() {
myRestServerR4Helper.clearDataAndCounts();
}
@AfterAll
public static void afterAll(){
TestUtil.randomizeLocaleAndTimezone();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testExportConceptMapToCsvCommand(boolean theIncludeTls) throws IOException {
ourLog.info("ConceptMap:\n" + myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
ExportConceptMapToCsvCommand.COMMAND,
"-v", myVersion,
"-u", CM_URL, "-u", CM_URL,
"-f", FILE, "-f", FILE,
"-l"}); "-l"
},
"-t", theIncludeTls, myRestServerR4Helper
));
await().until(() -> new File(FILE).exists()); await().until(() -> new File(FILE).exists());
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" + String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
@ -78,36 +99,6 @@ public class ExportConceptMapToCsvCommandR4Test {
FileUtils.deleteQuietly(new File(FILE)); FileUtils.deleteQuietly(new File(FILE));
} }
@AfterAll
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
TestUtil.randomizeLocaleAndTimezone();
}
@BeforeAll
public static void beforeClass() throws Exception {
ourServer = new Server(0);
ServletHandler servletHandler = new ServletHandler();
RestfulServer restfulServer = new RestfulServer(ourCtx);
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapR4(ourCtx));
ServletHolder servletHolder = new ServletHolder(restfulServer);
servletHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(servletHandler);
JettyUtil.startServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourBase = "http://localhost:" + ourPort;
ourClient = ourCtx.newRestfulGenericClient(ourBase);
ourClient.create().resource(createConceptMap()).execute();
}
static ConceptMap createConceptMap() { static ConceptMap createConceptMap() {
ConceptMap conceptMap = new ConceptMap(); ConceptMap conceptMap = new ConceptMap();
conceptMap conceptMap

View File

@ -2,14 +2,9 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.test.utilities.TlsAuthenticationTestHelper;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.test.utilities.RestServerDstu3Helper;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.ConceptMap; import org.hl7.fhir.dstu3.model.ConceptMap;
import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent; import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent;
@ -18,9 +13,11 @@ import org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence; import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.File; import java.io.File;
@ -37,26 +34,27 @@ public class ImportCsvToConceptMapCommandDstu3Test {
private static final String CS_URL_3 = "http://example.com/codesystem/3"; private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILENAME = "import-csv-to-conceptmap-command-test-input.csv"; private static final String FILENAME = "import-csv-to-conceptmap-command-test-input.csv";
private static String file; private final FhirContext myCtx = FhirContext.forDstu3();
private static String ourBase; private final String myVersion = "dstu3";
private static IGenericClient ourClient; private String myFile;
private static FhirContext ourCtx = FhirContext.forDstu3();
private static int ourPort;
private static Server ourServer;
private static String ourVersion = "dstu3";
private static RestfulServer restfulServer;
private static HashMapResourceProviderConceptMapDstu3 hashMapResourceProviderConceptMapDstu3;
static { static {
System.setProperty("test", "true"); System.setProperty("test", "true");
} }
@AfterEach @RegisterExtension
public void afterClearResourceProvider() { public final RestServerDstu3Helper myRestServerDstu3Helper = new RestServerDstu3Helper(true);
HashMapResourceProviderConceptMapDstu3 resourceProvider = (HashMapResourceProviderConceptMapDstu3) restfulServer.getResourceProviders().iterator().next(); @RegisterExtension
resourceProvider.clear(); public TlsAuthenticationTestHelper myTlsAuthenticationTestHelper = new TlsAuthenticationTestHelper();
@BeforeEach
public void before(){
myRestServerDstu3Helper.setConceptMapResourceProvider(new HashMapResourceProviderConceptMapDstu3(myCtx));
}
@AfterAll
public static void afterAll(){
TestUtil.randomizeLocaleAndTimezone();
} }
@Test @Test
@ -65,7 +63,7 @@ public class ImportCsvToConceptMapCommandDstu3Test {
String conceptMapUrl = conceptMap.getUrl(); String conceptMapUrl = conceptMap.getUrl();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl); ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = ourClient MethodOutcome methodOutcome = myRestServerDstu3Helper.getClient()
.update() .update()
.resource(conceptMap) .resource(conceptMap)
.conditional() .conditional()
@ -78,11 +76,11 @@ public class ImportCsvToConceptMapCommandDstu3Test {
@Test @Test
public void testConditionalUpdateResultsInUpdate() { public void testConditionalUpdateResultsInUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap(); ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap();
ourClient.create().resource(conceptMap).execute(); myRestServerDstu3Helper.getClient().create().resource(conceptMap).execute();
String conceptMapUrl = conceptMap.getUrl(); String conceptMapUrl = conceptMap.getUrl();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl); ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = ourClient MethodOutcome methodOutcome = myRestServerDstu3Helper.getClient()
.update() .update()
.resource(conceptMap) .resource(conceptMap)
.conditional() .conditional()
@ -95,9 +93,9 @@ public class ImportCsvToConceptMapCommandDstu3Test {
@Test @Test
public void testNonConditionalUpdate() { public void testNonConditionalUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap(); ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap();
ourClient.create().resource(conceptMap).execute(); myRestServerDstu3Helper.getClient().create().resource(conceptMap).execute();
Bundle response = ourClient Bundle response = myRestServerDstu3Helper.getClient()
.search() .search()
.forResource(ConceptMap.class) .forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL)) .where(ConceptMap.URL.matches().value(CM_URL))
@ -106,7 +104,7 @@ public class ImportCsvToConceptMapCommandDstu3Test {
ConceptMap resultConceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); ConceptMap resultConceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
MethodOutcome methodOutcome = ourClient MethodOutcome methodOutcome = myRestServerDstu3Helper.getClient()
.update() .update()
.resource(resultConceptMap) .resource(resultConceptMap)
.withId(resultConceptMap.getIdElement()) .withId(resultConceptMap.getIdElement())
@ -115,22 +113,27 @@ public class ImportCsvToConceptMapCommandDstu3Test {
assertNull(methodOutcome.getCreated()); assertNull(methodOutcome.getCreated());
} }
@Test @ParameterizedTest
public void testImportCsvToConceptMapCommand() throws FHIRException { @ValueSource(booleans = {true, false})
public void testImportCsvToConceptMapCommandNoTls(boolean theIncludeTls) throws FHIRException {
ClassLoader classLoader = getClass().getClassLoader(); ClassLoader classLoader = getClass().getClassLoader();
File fileToImport = new File(classLoader.getResource(FILENAME).getFile()); File fileToImport = new File(classLoader.getResource(FILENAME).getFile());
ImportCsvToConceptMapCommandDstu3Test.file = fileToImport.getAbsolutePath(); myFile = fileToImport.getAbsolutePath();
App.main(new String[]{"import-csv-to-conceptmap", App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
"-v", ourVersion, new String[]{
"-t", ourBase, ImportCsvToConceptMapCommand.COMMAND,
"-v", myVersion,
"-u", CM_URL, "-u", CM_URL,
"-i", VS_URL_1, "-i", VS_URL_1,
"-o", VS_URL_2, "-o", VS_URL_2,
"-f", file, "-f", myFile,
"-l"}); "-l"
},
"-t", theIncludeTls, myRestServerDstu3Helper
));
Bundle response = ourClient Bundle response = myRestServerDstu3Helper.getClient()
.search() .search()
.forResource(ConceptMap.class) .forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL)) .where(ConceptMap.URL.matches().value(CM_URL))
@ -139,9 +142,9 @@ public class ImportCsvToConceptMapCommandDstu3Test {
ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); ourLog.info(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/1", conceptMap.getId()); assertEquals(myRestServerDstu3Helper.getBase() + "/ConceptMap/1/_history/1", conceptMap.getId());
assertEquals(CM_URL, conceptMap.getUrl()); assertEquals(CM_URL, conceptMap.getUrl());
assertEquals(VS_URL_1, conceptMap.getSourceUriType().getValueAsString()); assertEquals(VS_URL_1, conceptMap.getSourceUriType().getValueAsString());
@ -317,16 +320,20 @@ public class ImportCsvToConceptMapCommandDstu3Test {
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence()); assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3d This is a comment.", target.getComment()); assertEquals("3d This is a comment.", target.getComment());
App.main(new String[]{"import-csv-to-conceptmap", App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
"-v", ourVersion, new String[]{
"-t", ourBase, ImportCsvToConceptMapCommand.COMMAND,
"-v", myVersion,
"-u", CM_URL, "-u", CM_URL,
"-i", VS_URL_1, "-i", VS_URL_1,
"-o", VS_URL_2, "-o", VS_URL_2,
"-f", file, "-f", myFile,
"-l"}); "-l"
},
"-t", theIncludeTls, myRestServerDstu3Helper
));
response = ourClient response = myRestServerDstu3Helper.getClient()
.search() .search()
.forResource(ConceptMap.class) .forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL)) .where(ConceptMap.URL.matches().value(CM_URL))
@ -335,34 +342,7 @@ public class ImportCsvToConceptMapCommandDstu3Test {
conceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/2", conceptMap.getId()); assertEquals(myRestServerDstu3Helper.getBase() + "/ConceptMap/1/_history/2", conceptMap.getId());
} }
@AfterAll
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
TestUtil.randomizeLocaleAndTimezone();
}
@BeforeAll
public static void beforeClass() throws Exception {
ourServer = new Server(0);
ServletHandler servletHandler = new ServletHandler();
restfulServer = new RestfulServer(ourCtx);
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapDstu3(ourCtx));
ServletHolder servletHolder = new ServletHolder(restfulServer);
servletHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(servletHandler);
JettyUtil.startServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourBase = "http://localhost:" + ourPort;
ourClient = ourCtx.newRestfulGenericClient(ourBase);
}
} }

View File

@ -2,14 +2,9 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.test.utilities.TlsAuthenticationTestHelper;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.test.utilities.RestServerR4Helper;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ConceptMap;
@ -18,9 +13,11 @@ import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent; import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.File; import java.io.File;
@ -38,26 +35,27 @@ public class ImportCsvToConceptMapCommandR4Test {
private static final String CS_URL_3 = "http://example.com/codesystem/3"; private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILENAME = "import-csv-to-conceptmap-command-test-input.csv"; private static final String FILENAME = "import-csv-to-conceptmap-command-test-input.csv";
private static String file;
private static String ourBase;
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static int ourPort;
private static Server ourServer;
private static String ourVersion = "r4";
private static RestfulServer restfulServer;
private static HashMapResourceProviderConceptMapR4 hashMapResourceProviderConceptMapR4;
static { static {
System.setProperty("test", "true"); System.setProperty("test", "true");
} }
@AfterEach private final FhirContext myFhirContext = FhirContext.forR4();
public void afterClearResourceProvider() { private final String myVersion = "r4";
HashMapResourceProviderConceptMapR4 resourceProvider = (HashMapResourceProviderConceptMapR4) restfulServer.getResourceProviders().iterator().next(); private String myFilePath;
resourceProvider.clear();
@RegisterExtension
public final RestServerR4Helper myRestServerR4Helper = new RestServerR4Helper(true);
@RegisterExtension
public TlsAuthenticationTestHelper myTlsAuthenticationTestHelper = new TlsAuthenticationTestHelper();
@BeforeEach
public void before(){
myRestServerR4Helper.setConceptMapResourceProvider(new HashMapResourceProviderConceptMapR4(myFhirContext));
}
@AfterAll
public static void afterAll(){
TestUtil.randomizeLocaleAndTimezone();
} }
@Test @Test
@ -66,7 +64,7 @@ public class ImportCsvToConceptMapCommandR4Test {
String conceptMapUrl = conceptMap.getUrl(); String conceptMapUrl = conceptMap.getUrl();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl); ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = ourClient MethodOutcome methodOutcome = myRestServerR4Helper.getClient()
.update() .update()
.resource(conceptMap) .resource(conceptMap)
.conditional() .conditional()
@ -80,11 +78,11 @@ public class ImportCsvToConceptMapCommandR4Test {
@Test @Test
public void testConditionalUpdateResultsInUpdate() { public void testConditionalUpdateResultsInUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap(); ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap();
ourClient.create().resource(conceptMap).execute(); myRestServerR4Helper.getClient().create().resource(conceptMap).execute();
String conceptMapUrl = conceptMap.getUrl(); String conceptMapUrl = conceptMap.getUrl();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl); ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = ourClient MethodOutcome methodOutcome = myRestServerR4Helper.getClient()
.update() .update()
.resource(conceptMap) .resource(conceptMap)
.conditional() .conditional()
@ -98,9 +96,9 @@ public class ImportCsvToConceptMapCommandR4Test {
@Test @Test
public void testNonConditionalUpdate() { public void testNonConditionalUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap(); ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap();
ourClient.create().resource(conceptMap).execute(); myRestServerR4Helper.getClient().create().resource(conceptMap).execute();
Bundle response = ourClient Bundle response = myRestServerR4Helper.getClient()
.search() .search()
.forResource(ConceptMap.class) .forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL)) .where(ConceptMap.URL.matches().value(CM_URL))
@ -109,7 +107,7 @@ public class ImportCsvToConceptMapCommandR4Test {
ConceptMap resultConceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); ConceptMap resultConceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
MethodOutcome methodOutcome = ourClient MethodOutcome methodOutcome = myRestServerR4Helper.getClient()
.update() .update()
.resource(resultConceptMap) .resource(resultConceptMap)
.withId(resultConceptMap.getIdElement()) .withId(resultConceptMap.getIdElement())
@ -121,22 +119,27 @@ public class ImportCsvToConceptMapCommandR4Test {
assertTrue(!Boolean.TRUE.equals(methodOutcome.getCreated())); assertTrue(!Boolean.TRUE.equals(methodOutcome.getCreated()));
} }
@Test @ParameterizedTest
public void testImportCsvToConceptMapCommand() throws FHIRException { @ValueSource(booleans = {true, false})
public void testImportCsvToConceptMapCommand(boolean theIncludeTls) throws Exception {
ClassLoader classLoader = getClass().getClassLoader(); ClassLoader classLoader = getClass().getClassLoader();
File fileToImport = new File(classLoader.getResource(FILENAME).getFile()); File fileToImport = new File(classLoader.getResource(FILENAME).getFile());
ImportCsvToConceptMapCommandR4Test.file = fileToImport.getAbsolutePath(); myFilePath = fileToImport.getAbsolutePath();
App.main(new String[]{"import-csv-to-conceptmap", App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
"-v", ourVersion, new String[]{
"-t", ourBase, ImportCsvToConceptMapCommand.COMMAND,
"-v", myVersion,
"-u", CM_URL, "-u", CM_URL,
"-i", VS_URL_1, "-i", VS_URL_1,
"-o", VS_URL_2, "-o", VS_URL_2,
"-f", file, "-f", myFilePath,
"-l"}); "-l"
},
"-t", theIncludeTls, myRestServerR4Helper
));
Bundle response = ourClient Bundle response = myRestServerR4Helper.getClient()
.search() .search()
.forResource(ConceptMap.class) .forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL)) .where(ConceptMap.URL.matches().value(CM_URL))
@ -145,9 +148,9 @@ public class ImportCsvToConceptMapCommandR4Test {
ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/1", conceptMap.getId()); assertEquals(myRestServerR4Helper.getBase() + "/ConceptMap/1/_history/1", conceptMap.getId());
assertEquals(CM_URL, conceptMap.getUrl()); assertEquals(CM_URL, conceptMap.getUrl());
assertEquals(VS_URL_1, conceptMap.getSourceUriType().getValueAsString()); assertEquals(VS_URL_1, conceptMap.getSourceUriType().getValueAsString());
@ -323,16 +326,20 @@ public class ImportCsvToConceptMapCommandR4Test {
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence()); assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3d This is a comment.", target.getComment()); assertEquals("3d This is a comment.", target.getComment());
App.main(new String[]{"import-csv-to-conceptmap", App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
"-v", ourVersion, new String[]{
"-t", ourBase, ImportCsvToConceptMapCommand.COMMAND,
"-v", myVersion,
"-u", CM_URL, "-u", CM_URL,
"-i", VS_URL_1, "-i", VS_URL_1,
"-o", VS_URL_2, "-o", VS_URL_2,
"-f", file, "-f", myFilePath,
"-l"}); "-l"
},
"-t", theIncludeTls, myRestServerR4Helper
));
response = ourClient response = myRestServerR4Helper.getClient()
.search() .search()
.forResource(ConceptMap.class) .forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL)) .where(ConceptMap.URL.matches().value(CM_URL))
@ -341,25 +348,30 @@ public class ImportCsvToConceptMapCommandR4Test {
conceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/2", conceptMap.getId()); assertEquals(myRestServerR4Helper.getBase() + "/ConceptMap/1/_history/2", conceptMap.getId());
} }
@Test @ParameterizedTest
public void testImportCsvToConceptMapCommandWithByteOrderMark() throws FHIRException { @ValueSource(booleans = {true, false})
public void testImportCsvToConceptMapCommandWithByteOrderMark(boolean theIncludeTls) throws FHIRException {
ClassLoader classLoader = getClass().getClassLoader(); ClassLoader classLoader = getClass().getClassLoader();
File fileToImport = new File(classLoader.getResource("loinc-to-phenx.csv").getFile()); File fileToImport = new File(classLoader.getResource("loinc-to-phenx.csv").getFile());
ImportCsvToConceptMapCommandR4Test.file = fileToImport.getAbsolutePath(); myFilePath = fileToImport.getAbsolutePath();
App.main(new String[]{"import-csv-to-conceptmap", App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
"-v", ourVersion, new String[]{
"-t", ourBase, ImportCsvToConceptMapCommand.COMMAND,
"-v", myVersion,
"-u", "http://loinc.org/cm/loinc-to-phenx", "-u", "http://loinc.org/cm/loinc-to-phenx",
"-i", "http://loinc.org", "-i", "http://loinc.org",
"-o", "http://phenxtoolkit.org", "-o", "http://phenxtoolkit.org",
"-f", file, "-f", myFilePath,
"-l"}); "-l"
},
"-t", theIncludeTls, myRestServerR4Helper
));
Bundle response = ourClient Bundle response = myRestServerR4Helper.getClient()
.search() .search()
.forResource(ConceptMap.class) .forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value("http://loinc.org/cm/loinc-to-phenx")) .where(ConceptMap.URL.matches().value("http://loinc.org/cm/loinc-to-phenx"))
@ -368,9 +380,9 @@ public class ImportCsvToConceptMapCommandR4Test {
ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/1", conceptMap.getId()); assertEquals(myRestServerR4Helper.getBase() + "/ConceptMap/1/_history/1", conceptMap.getId());
assertEquals("http://loinc.org/cm/loinc-to-phenx", conceptMap.getUrl()); assertEquals("http://loinc.org/cm/loinc-to-phenx", conceptMap.getUrl());
assertEquals("http://loinc.org", conceptMap.getSourceUriType().getValueAsString()); assertEquals("http://loinc.org", conceptMap.getSourceUriType().getValueAsString());
@ -398,16 +410,20 @@ public class ImportCsvToConceptMapCommandR4Test {
assertEquals(ConceptMapEquivalence.EQUIVALENT, target.getEquivalence()); assertEquals(ConceptMapEquivalence.EQUIVALENT, target.getEquivalence());
assertNull(target.getComment()); assertNull(target.getComment());
App.main(new String[]{"import-csv-to-conceptmap", App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
"-v", ourVersion, new String[]{
"-t", ourBase, ImportCsvToConceptMapCommand.COMMAND,
"-v", myVersion,
"-u", "http://loinc.org/cm/loinc-to-phenx", "-u", "http://loinc.org/cm/loinc-to-phenx",
"-i", "http://loinc.org", "-i", "http://loinc.org",
"-o", "http://phenxtoolkit.org", "-o", "http://phenxtoolkit.org",
"-f", file, "-f", myFilePath,
"-l"}); "-l"
},
"-t", theIncludeTls, myRestServerR4Helper
));
response = ourClient response = myRestServerR4Helper.getClient()
.search() .search()
.forResource(ConceptMap.class) .forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value("http://loinc.org/cm/loinc-to-phenx")) .where(ConceptMap.URL.matches().value("http://loinc.org/cm/loinc-to-phenx"))
@ -416,34 +432,6 @@ public class ImportCsvToConceptMapCommandR4Test {
conceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/2", conceptMap.getId()); assertEquals(myRestServerR4Helper.getBase() + "/ConceptMap/1/_history/2", conceptMap.getId());
}
@AfterAll
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
TestUtil.randomizeLocaleAndTimezone();
}
@BeforeAll
public static void beforeClass() throws Exception {
ourServer = new Server(0);
ServletHandler servletHandler = new ServletHandler();
restfulServer = new RestfulServer(ourCtx);
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapR4(ourCtx));
ServletHolder servletHolder = new ServletHolder(restfulServer);
servletHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(servletHandler);
JettyUtil.startServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourBase = "http://localhost:" + ourPort;
ourClient = ourCtx.newRestfulGenericClient(ourBase);
} }
} }

View File

@ -2,12 +2,15 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider; import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; import ca.uhn.fhir.test.utilities.RestServerR4Helper;
import ca.uhn.fhir.test.utilities.TlsAuthenticationTestHelper;
import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@ -17,7 +20,7 @@ import java.io.PrintStream;
import static ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider.RESP_PARAM_SUCCESS; import static ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider.RESP_PARAM_SUCCESS;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@ -31,8 +34,9 @@ class ReindexTerminologyCommandTest {
private BaseJpaSystemProvider<?, ?> myProvider = spy(new BaseJpaSystemProvider<>()); private BaseJpaSystemProvider<?, ?> myProvider = spy(new BaseJpaSystemProvider<>());
@RegisterExtension @RegisterExtension
public final RestfulServerExtension myRestfulServerExtension = public final RestServerR4Helper myRestServerR4Helper = new RestServerR4Helper(true);
new RestfulServerExtension(myContext, myProvider); @RegisterExtension
public TlsAuthenticationTestHelper myTlsAuthenticationTestHelper = new TlsAuthenticationTestHelper();
private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
@ -41,65 +45,89 @@ class ReindexTerminologyCommandTest {
System.setProperty("test", "true"); System.setProperty("test", "true");
} }
@BeforeEach
public void beforeEach(){
myRestServerR4Helper.registerProvider(myProvider);
}
@Test @ParameterizedTest
public void testProviderMethodInvoked() { @ValueSource(booleans = {true, false})
public void testProviderMethodInvoked(boolean theIncludeTls) {
System.setOut(new PrintStream(outputStreamCaptor)); System.setOut(new PrintStream(outputStreamCaptor));
IBaseParameters retVal = ParametersUtil.newInstance(myContext); IBaseParameters retVal = ParametersUtil.newInstance(myContext);
ParametersUtil.addParameterToParametersBoolean(myContext, retVal, RESP_PARAM_SUCCESS, true); ParametersUtil.addParameterToParametersBoolean(myContext, retVal, RESP_PARAM_SUCCESS, true);
doReturn(retVal).when(myProvider).reindexTerminology(any()); doReturn(retVal).when(myProvider).reindexTerminology(any());
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
ReindexTerminologyCommand.REINDEX_TERMINOLOGY, ReindexTerminologyCommand.REINDEX_TERMINOLOGY,
"-v", "r4", "-v", "r4"
"-t", myRestfulServerExtension.getBaseUrl() },
}); "-t", theIncludeTls, myRestServerR4Helper
));
assertThat(outputStreamCaptor.toString().trim(), assertThat(outputStreamCaptor.toString().trim(),
outputStreamCaptor.toString().trim(), containsString("<valueBoolean value=\"true\"/>")); outputStreamCaptor.toString().trim(), containsString("<valueBoolean value=\"true\"/>"));
} }
@Test @ParameterizedTest
public void testNoVersionThrows() { @ValueSource(booleans = {true, false})
public void testNoVersionThrows(boolean theIncludeTls) {
IBaseParameters retVal = ParametersUtil.newInstance(myContext); IBaseParameters retVal = ParametersUtil.newInstance(myContext);
ParametersUtil.addParameterToParametersBoolean(myContext, retVal, RESP_PARAM_SUCCESS, true); ParametersUtil.addParameterToParametersBoolean(myContext, retVal, RESP_PARAM_SUCCESS, true);
doReturn(retVal).when(myProvider).reindexTerminology(any()); doReturn(retVal).when(myProvider).reindexTerminology(any());
Error thrown = assertThrows(Error.class, () -> try {
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
ReindexTerminologyCommand.REINDEX_TERMINOLOGY
},
"-t", theIncludeTls, myRestServerR4Helper
));
fail();
} catch (Error e) {
assertThat(e.getMessage(), containsString("Missing required option: v"));
}
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testNoTargetThrows(boolean theIncludeTls) {
IBaseParameters retVal = ParametersUtil.newInstance(myContext);
ParametersUtil.addParameterToParametersBoolean(myContext, retVal, RESP_PARAM_SUCCESS, true);
doReturn(retVal).when(myProvider).reindexTerminology(any());
try {
App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
ReindexTerminologyCommand.REINDEX_TERMINOLOGY, ReindexTerminologyCommand.REINDEX_TERMINOLOGY,
"-t", myRestfulServerExtension.getBaseUrl() "-v", "r4"
}) },
); null, theIncludeTls, myRestServerR4Helper
assertThat(thrown.getMessage(), containsString("Missing required option: v")); ));
fail();
} catch (Error e) {
assertThat(e.getMessage(), containsString("Missing required option: t"));
}
} }
@Test @ParameterizedTest
public void testNoTargetThrows() { @ValueSource(booleans = {true, false})
IBaseParameters retVal = ParametersUtil.newInstance(myContext); public void testHandleUnexpectedResponse(boolean theIncludeTls) {
ParametersUtil.addParameterToParametersBoolean(myContext, retVal, RESP_PARAM_SUCCESS, true);
doReturn(retVal).when(myProvider).reindexTerminology(any());
Error thrown = assertThrows(Error.class, () ->
App.main(new String[]{ReindexTerminologyCommand.REINDEX_TERMINOLOGY, "-v", "r4"})
);
assertThat(thrown.getMessage(), containsString("Missing required option: t"));
}
@Test
public void testHandleUnexpectedResponse() {
System.setOut(new PrintStream(outputStreamCaptor)); System.setOut(new PrintStream(outputStreamCaptor));
IBaseParameters retVal = ParametersUtil.newInstance(myContext); IBaseParameters retVal = ParametersUtil.newInstance(myContext);
doReturn(retVal).when(myProvider).reindexTerminology(any()); doReturn(retVal).when(myProvider).reindexTerminology(any());
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
ReindexTerminologyCommand.REINDEX_TERMINOLOGY, ReindexTerminologyCommand.REINDEX_TERMINOLOGY,
"-v", "r4", "-v", "r4"
"-t", myRestfulServerExtension.getBaseUrl() },
}); "-t", theIncludeTls, myRestServerR4Helper
));
assertThat(outputStreamCaptor.toString().trim(), assertThat(outputStreamCaptor.toString().trim(),
outputStreamCaptor.toString().trim(), containsString("<valueBoolean value=\"false\"/>")); outputStreamCaptor.toString().trim(), containsString("<valueBoolean value=\"false\"/>"));
@ -110,8 +138,9 @@ class ReindexTerminologyCommandTest {
} }
@Test @ParameterizedTest
public void testHandleServiceError() { @ValueSource(booleans = {true, false})
public void testHandleServiceError(boolean theIncludeTls) {
System.setOut(new PrintStream(outputStreamCaptor)); System.setOut(new PrintStream(outputStreamCaptor));
IBaseParameters retVal = ParametersUtil.newInstance(myContext); IBaseParameters retVal = ParametersUtil.newInstance(myContext);
ParametersUtil.addParameterToParametersBoolean(myContext, retVal, RESP_PARAM_SUCCESS, false); ParametersUtil.addParameterToParametersBoolean(myContext, retVal, RESP_PARAM_SUCCESS, false);
@ -119,11 +148,13 @@ class ReindexTerminologyCommandTest {
"Freetext service is not configured. Operation didn't run."); "Freetext service is not configured. Operation didn't run.");
doReturn(retVal).when(myProvider).reindexTerminology(any()); doReturn(retVal).when(myProvider).reindexTerminology(any());
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
ReindexTerminologyCommand.REINDEX_TERMINOLOGY, ReindexTerminologyCommand.REINDEX_TERMINOLOGY,
"-v", "r4", "-v", "r4"
"-t", myRestfulServerExtension.getBaseUrl() },
}); "-t", theIncludeTls, myRestServerR4Helper
));
assertThat(outputStreamCaptor.toString().trim(), assertThat(outputStreamCaptor.toString().trim(),
outputStreamCaptor.toString().trim(), containsString("<valueBoolean value=\"false\"/>")); outputStreamCaptor.toString().trim(), containsString("<valueBoolean value=\"false\"/>"));

View File

@ -5,20 +5,21 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.term.UploadStatistics; import ca.uhn.fhir.jpa.term.UploadStatistics;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.test.utilities.TlsAuthenticationTestHelper;
import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.test.utilities.BaseRestServerHelper;
import ca.uhn.fhir.test.utilities.RestServerDstu3Helper;
import ca.uhn.fhir.test.utilities.RestServerR4Helper;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
@ -69,8 +70,6 @@ public class UploadTerminologyCommandTest {
private final String myICD10URL = "http://hl7.org/fhir/sid/icd-10-cm"; private final String myICD10URL = "http://hl7.org/fhir/sid/icd-10-cm";
private final String myICD10FileName = new File("src/test/resources").getAbsolutePath() + "/icd10cm_tabular_2021.xml"; private final String myICD10FileName = new File("src/test/resources").getAbsolutePath() + "/icd10cm_tabular_2021.xml";
private File myICD10File = new File(myICD10FileName); private File myICD10File = new File(myICD10FileName);
private Server myServer;
private int myPort;
@Mock @Mock
protected ITermLoaderSvc myTermLoaderSvc; protected ITermLoaderSvc myTermLoaderSvc;
@ -82,35 +81,43 @@ public class UploadTerminologyCommandTest {
System.setProperty("test", "true"); System.setProperty("test", "true");
} }
static Stream<String> paramsProvider() { static Stream<Arguments> paramsProvider(){
return Stream.of(FHIR_VERSION_DSTU3, FHIR_VERSION_R4); return Stream.of(
// [0] theFhirVersion, [1] theIncludeTls
Arguments.arguments(FHIR_VERSION_DSTU3, true),
Arguments.arguments(FHIR_VERSION_DSTU3, false),
Arguments.arguments(FHIR_VERSION_R4, true),
Arguments.arguments(FHIR_VERSION_R4, false)
);
} }
@RegisterExtension
public final RestServerR4Helper myRestServerR4Helper = new RestServerR4Helper(true);
@RegisterExtension
public final RestServerDstu3Helper myRestServerDstu3Helper = new RestServerDstu3Helper(true);
@RegisterExtension
public TlsAuthenticationTestHelper myTlsAuthenticationTestHelper = new TlsAuthenticationTestHelper();
private BaseRestServerHelper myBaseRestServerHelper;
@BeforeEach @BeforeEach
public void beforeEach(TestInfo testInfo) throws Exception { public void beforeEach(TestInfo testInfo) throws Exception {
writeConceptAndHierarchyFiles(); writeConceptAndHierarchyFiles();
if (testInfo.getDisplayName().endsWith(FHIR_VERSION_DSTU3)) { if (testInfo.getDisplayName().contains(FHIR_VERSION_DSTU3)) {
myCtx = FhirContext.forDstu3(); myCtx = FhirContext.forDstu3();
} else if (testInfo.getDisplayName().endsWith(FHIR_VERSION_R4)) { myRestServerDstu3Helper.registerProvider(new TerminologyUploaderProvider(myCtx, myTermLoaderSvc));
myBaseRestServerHelper = myRestServerDstu3Helper;
} else if (testInfo.getDisplayName().contains(FHIR_VERSION_R4)) {
myCtx = FhirContext.forR4(); myCtx = FhirContext.forR4();
myRestServerR4Helper.registerProvider(new TerminologyUploaderProvider(myCtx, myTermLoaderSvc));
myBaseRestServerHelper = myRestServerR4Helper;
} else { } else {
fail("Unknown FHIR Version param provided: " + testInfo.getDisplayName()); fail("Unknown FHIR Version param provided: " + testInfo.getDisplayName());
} }
myServer = new Server(0);
TerminologyUploaderProvider provider = new TerminologyUploaderProvider(myCtx, myTermLoaderSvc);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(myCtx);
servlet.registerProvider(provider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
myServer.setHandler(proxyHandler);
JettyUtil.startServer(myServer);
myPort = JettyUtil.getPortForStartedServer(myServer);
} }
@AfterEach @AfterEach
public void afterEach() throws Exception { public void afterEach() throws Exception {
JettyUtil.closeServer(myServer);
FileUtils.deleteQuietly(myConceptsFile); FileUtils.deleteQuietly(myConceptsFile);
FileUtils.deleteQuietly(myHierarchyFile); FileUtils.deleteQuietly(myHierarchyFile);
FileUtils.deleteQuietly(myCodeSystemFile); FileUtils.deleteQuietly(myCodeSystemFile);
@ -122,7 +129,7 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testDeltaAdd(String theFhirVersion) throws IOException { public void testDeltaAdd(String theFhirVersion, boolean theIncludeTls) throws IOException {
if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) { if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) {
when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.dstu3.model.IdType("CodeSystem/101"))); when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.dstu3.model.IdType("CodeSystem/101")));
} else if (FHIR_VERSION_R4.equals(theFhirVersion)) { } else if (FHIR_VERSION_R4.equals(theFhirVersion)) {
@ -131,15 +138,17 @@ public class UploadTerminologyCommandTest {
fail("Unknown FHIR Version param provided: " + theFhirVersion); fail("Unknown FHIR Version param provided: " + theFhirVersion);
} }
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "ADD", "-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myConceptsFileName, "-d", myConceptsFileName,
"-d", myHierarchyFileName "-d", myHierarchyFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any()); verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any());
@ -151,7 +160,7 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testDeltaAddUsingCodeSystemResource(String theFhirVersion) throws IOException { public void testDeltaAddUsingCodeSystemResource(String theFhirVersion, boolean theIncludeTls) throws IOException {
if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) { if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) {
try (FileWriter w = new FileWriter(myCodeSystemFile, false)) { try (FileWriter w = new FileWriter(myCodeSystemFile, false)) {
org.hl7.fhir.dstu3.model.CodeSystem cs = new org.hl7.fhir.dstu3.model.CodeSystem(); org.hl7.fhir.dstu3.model.CodeSystem cs = new org.hl7.fhir.dstu3.model.CodeSystem();
@ -170,14 +179,16 @@ public class UploadTerminologyCommandTest {
when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.r4.model.IdType("CodeSystem/101"))); when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.r4.model.IdType("CodeSystem/101")));
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "ADD", "-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myCodeSystemFileName "-d", myCodeSystemFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any()); verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any());
@ -190,7 +201,7 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testDeltaAddInvalidResource(String theFhirVersion) throws IOException { public void testDeltaAddInvalidResource(String theFhirVersion, boolean theIncludeTls) throws IOException {
if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) { if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) {
try (FileWriter w = new FileWriter(myCodeSystemFile, false)) { try (FileWriter w = new FileWriter(myCodeSystemFile, false)) {
org.hl7.fhir.dstu3.model.Patient patient = new org.hl7.fhir.dstu3.model.Patient(); org.hl7.fhir.dstu3.model.Patient patient = new org.hl7.fhir.dstu3.model.Patient();
@ -208,14 +219,16 @@ public class UploadTerminologyCommandTest {
} }
try { try {
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "ADD", "-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myCodeSystemFileName "-d", myCodeSystemFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
fail(); fail();
} catch (Error e) { } catch (Error e) {
assertThat(e.toString(), containsString("HTTP 400 Bad Request: " + Msg.code(362) + "Request has parameter codeSystem of type Patient but method expects type CodeSystem")); assertThat(e.toString(), containsString("HTTP 400 Bad Request: " + Msg.code(362) + "Request has parameter codeSystem of type Patient but method expects type CodeSystem"));
@ -224,20 +237,23 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testDeltaAddInvalidFileType(String theFhirVersion) throws IOException { public void testDeltaAddInvalidFileType(String theFhirVersion, boolean theIncludeTls) throws IOException {
try (FileWriter w = new FileWriter(myTextFileName, false)) { try (FileWriter w = new FileWriter(myTextFileName, false)) {
w.append("Help I'm a Bug"); w.append("Help I'm a Bug");
} }
try { try {
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "ADD", "-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myTextFileName "-d", myTextFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
fail(); fail();
} catch (Error e) { } catch (Error e) {
assertThat(e.toString(), containsString("Don't know how to handle file:")); assertThat(e.toString(), containsString("Don't know how to handle file:"));
@ -246,19 +262,21 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testDeltaAddUsingCompressedFile(String theFhirVersion) throws IOException { public void testDeltaAddUsingCompressedFile(String theFhirVersion, boolean theIncludeTls) throws IOException {
writeArchiveFile(myConceptsFile, myHierarchyFile); writeArchiveFile(myConceptsFile, myHierarchyFile);
when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.r4.model.IdType("CodeSystem/101"))); when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.r4.model.IdType("CodeSystem/101")));
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "ADD", "-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myArchiveFileName "-d", myArchiveFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any()); verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any());
@ -270,17 +288,20 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testDeltaAddInvalidFileName(String theFhirVersion) throws IOException { public void testDeltaAddInvalidFileName(String theFhirVersion, boolean theIncludeTls) throws IOException {
try { try {
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "ADD", "-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myConceptsFileName + "/foo.csv", "-d", myConceptsFileName + "/foo.csv",
"-d", myHierarchyFileName "-d", myHierarchyFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
fail();
} catch (Error e) { } catch (Error e) {
assertThat(e.toString(), Matchers.containsString("FileNotFoundException: target/concepts.csv/foo.csv")); assertThat(e.toString(), Matchers.containsString("FileNotFoundException: target/concepts.csv/foo.csv"));
} }
@ -288,7 +309,7 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testDeltaRemove(String theFhirVersion) throws IOException { public void testDeltaRemove(String theFhirVersion, boolean theIncludeTls) throws IOException {
if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) { if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) {
when(myTermLoaderSvc.loadDeltaRemove(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.dstu3.model.IdType("CodeSystem/101"))); when(myTermLoaderSvc.loadDeltaRemove(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.dstu3.model.IdType("CodeSystem/101")));
} else if (FHIR_VERSION_R4.equals(theFhirVersion)) { } else if (FHIR_VERSION_R4.equals(theFhirVersion)) {
@ -297,15 +318,17 @@ public class UploadTerminologyCommandTest {
fail("Unknown FHIR Version param provided: " + theFhirVersion); fail("Unknown FHIR Version param provided: " + theFhirVersion);
} }
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "REMOVE", "-m", "REMOVE",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myConceptsFileName, "-d", myConceptsFileName,
"-d", myHierarchyFileName "-d", myHierarchyFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
verify(myTermLoaderSvc, times(1)).loadDeltaRemove(eq("http://foo"), myDescriptorListCaptor.capture(), any()); verify(myTermLoaderSvc, times(1)).loadDeltaRemove(eq("http://foo"), myDescriptorListCaptor.capture(), any());
@ -317,7 +340,7 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testSnapshot(String theFhirVersion) throws IOException { public void testSnapshot(String theFhirVersion, boolean theIncludeTls) throws IOException {
if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) { if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) {
when(myTermLoaderSvc.loadCustom(any(), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.dstu3.model.IdType("CodeSystem/101"))); when(myTermLoaderSvc.loadCustom(any(), anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.dstu3.model.IdType("CodeSystem/101")));
} else if (FHIR_VERSION_R4.equals(theFhirVersion)) { } else if (FHIR_VERSION_R4.equals(theFhirVersion)) {
@ -326,15 +349,17 @@ public class UploadTerminologyCommandTest {
fail("Unknown FHIR Version param provided: " + theFhirVersion); fail("Unknown FHIR Version param provided: " + theFhirVersion);
} }
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "SNAPSHOT", "-m", "SNAPSHOT",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myConceptsFileName, "-d", myConceptsFileName,
"-d", myHierarchyFileName "-d", myHierarchyFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any()); verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any());
@ -346,7 +371,7 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testPropertiesFile(String theFhirVersion) throws IOException { public void testPropertiesFile(String theFhirVersion, boolean theIncludeTls) throws IOException {
try (FileWriter w = new FileWriter(myPropertiesFileName, false)) { try (FileWriter w = new FileWriter(myPropertiesFileName, false)) {
w.append("a=b\n"); w.append("a=b\n");
} }
@ -359,14 +384,16 @@ public class UploadTerminologyCommandTest {
fail("Unknown FHIR Version param provided: " + theFhirVersion); fail("Unknown FHIR Version param provided: " + theFhirVersion);
} }
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "SNAPSHOT", "-m", "SNAPSHOT",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myPropertiesFileName, "-d", myPropertiesFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any()); verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any());
@ -378,7 +405,7 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testSnapshotLargeFile(String theFhirVersion) throws IOException { public void testSnapshotLargeFile(String theFhirVersion, boolean theIncludeTls) throws IOException {
UploadTerminologyCommand.setTransferSizeLimitForUnitTest(10); UploadTerminologyCommand.setTransferSizeLimitForUnitTest(10);
if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) { if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) {
@ -389,15 +416,17 @@ public class UploadTerminologyCommandTest {
fail("Unknown FHIR Version param provided: " + theFhirVersion); fail("Unknown FHIR Version param provided: " + theFhirVersion);
} }
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-m", "SNAPSHOT", "-m", "SNAPSHOT",
"-t", "http://localhost:" + myPort,
"-u", "http://foo", "-u", "http://foo",
"-d", myConceptsFileName, "-d", myConceptsFileName,
"-d", myHierarchyFileName "-d", myHierarchyFileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any()); verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any());
@ -409,7 +438,7 @@ public class UploadTerminologyCommandTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("paramsProvider") @MethodSource("paramsProvider")
public void testUploadICD10UsingCompressedFile(String theFhirVersion) throws IOException { public void testUploadICD10UsingCompressedFile(String theFhirVersion, boolean theIncludeTls) throws IOException {
if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) { if (FHIR_VERSION_DSTU3.equals(theFhirVersion)) {
when(myTermLoaderSvc.loadIcd10cm(anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.dstu3.model.IdType("CodeSystem/101"))); when(myTermLoaderSvc.loadIcd10cm(anyList(), any())).thenReturn(new UploadStatistics(100, new org.hl7.fhir.dstu3.model.IdType("CodeSystem/101")));
} else if (FHIR_VERSION_R4.equals(theFhirVersion)) { } else if (FHIR_VERSION_R4.equals(theFhirVersion)) {
@ -418,13 +447,15 @@ public class UploadTerminologyCommandTest {
fail("Unknown FHIR Version param provided: " + theFhirVersion); fail("Unknown FHIR Version param provided: " + theFhirVersion);
} }
App.main(new String[]{ App.main(myTlsAuthenticationTestHelper.createBaseRequestGeneratingCommandArgs(
new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY, UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", theFhirVersion, "-v", theFhirVersion,
"-t", "http://localhost:" + myPort,
"-u", myICD10URL, "-u", myICD10URL,
"-d", myICD10FileName "-d", myICD10FileName
}); },
"-t", theIncludeTls, myBaseRestServerHelper
));
verify(myTermLoaderSvc, times(1)).loadIcd10cm(myDescriptorListCaptor.capture(), any()); verify(myTermLoaderSvc, times(1)).loadIcd10cm(myDescriptorListCaptor.capture(), any());

View File

@ -0,0 +1,163 @@
package ca.uhn.fhir.cli.client;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import javax.net.ssl.SSLHandshakeException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class HapiFhirCliRestfulClientFactoryTest extends BaseFhirVersionParameterizedTest{
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testNativeClientHttp(FhirVersionEnum theFhirVersion) throws Exception {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
HapiFhirCliRestfulClientFactory clientFactory = new HapiFhirCliRestfulClientFactory(fhirVersionParams.getFhirContext());
HttpClient client = clientFactory.getNativeHttpClient();
HttpUriRequest request = new HttpGet(fhirVersionParams.getPatientEndpoint());
HttpResponse response = client.execute(request);
assertEquals(200, response.getStatusLine().getStatusCode());
String json = EntityUtils.toString(response.getEntity());
IBaseResource bundle = fhirVersionParams.parseResource(json);
assertEquals(fhirVersionParams.getFhirVersion(), bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testNativeClientHttps(FhirVersionEnum theFhirVersion) throws Exception {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
HapiFhirCliRestfulClientFactory clientFactory = new HapiFhirCliRestfulClientFactory(fhirVersionParams.getFhirContext(), getTlsAuthentication());
HttpClient authenticatedClient = clientFactory.getNativeHttpClient();
HttpUriRequest request = new HttpGet(fhirVersionParams.getSecuredPatientEndpoint());
HttpResponse response = authenticatedClient.execute(request);
assertEquals(200, response.getStatusLine().getStatusCode());
String json = EntityUtils.toString(response.getEntity());
IBaseResource bundle = fhirVersionParams.parseResource(json);
assertEquals(fhirVersionParams.getFhirVersion(), bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testNativeClientHttpsNoCredentials(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
HapiFhirCliRestfulClientFactory clientFactory = new HapiFhirCliRestfulClientFactory(fhirVersionParams.getFhirContext());
HttpClient unauthenticatedClient = clientFactory.getNativeHttpClient();
try{
HttpUriRequest request = new HttpGet(fhirVersionParams.getSecuredPatientEndpoint());
unauthenticatedClient.execute(request);
fail();
}
catch(Exception e){
assertEquals(SSLHandshakeException.class, e.getClass());
}
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testGenericClientHttp(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
String base = fhirVersionParams.getBase();
FhirContext context = fhirVersionParams.getFhirContext();
context.setRestfulClientFactory(new HapiFhirCliRestfulClientFactory(context));
IBaseResource bundle = context.newRestfulGenericClient(base).search().forResource("Patient").execute();
assertEquals(theFhirVersion, bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testGenericClientHttps(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
String secureBase = fhirVersionParams.getSecureBase();
FhirContext context = fhirVersionParams.getFhirContext();
context.setRestfulClientFactory(new HapiFhirCliRestfulClientFactory(context, getTlsAuthentication()));
IBaseResource bundle = context.newRestfulGenericClient(secureBase).search().forResource("Patient").execute();
assertEquals(theFhirVersion, bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testGenericClientHttpsNoCredentials(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
String secureBase = fhirVersionParams.getSecureBase();
FhirContext context = fhirVersionParams.getFhirContext();
context.setRestfulClientFactory(new HapiFhirCliRestfulClientFactory(context));
try {
context.newRestfulGenericClient(secureBase).search().forResource("Patient").execute();
fail();
} catch (Exception e) {
assertTrue(e.getMessage().contains("HAPI-1357: Failed to retrieve the server metadata statement during client initialization"));
assertEquals(SSLHandshakeException.class, e.getCause().getCause().getClass());
}
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testGenericClientProtocolChanges(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
FhirContext context = fhirVersionParams.getFhirContext();
String secureBase = fhirVersionParams.getSecureBase();
String base = fhirVersionParams.getBase();
// https
HapiFhirCliRestfulClientFactory restfulClientFactory = new HapiFhirCliRestfulClientFactory(context, getTlsAuthentication());
context.setRestfulClientFactory(restfulClientFactory);
IBaseResource bundle = context.newRestfulGenericClient(secureBase).search().forResource("Patient").execute();
assertEquals(theFhirVersion, bundle.getStructureFhirVersionEnum());
// http
restfulClientFactory.useHttp();
bundle = context.newRestfulGenericClient(base).search().forResource("Patient").execute();
assertEquals(theFhirVersion, bundle.getStructureFhirVersionEnum());
// https
restfulClientFactory.useHttps(getTlsAuthentication());
bundle = context.newRestfulGenericClient(secureBase).search().forResource("Patient").execute();
assertEquals(theFhirVersion, bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testSetHttpClient(FhirVersionEnum theFhirVersion){
try {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
FhirContext fhirContext = fhirVersionParams.getFhirContext();
HapiFhirCliRestfulClientFactory hapiFhirCliRestfulClientFactory = new HapiFhirCliRestfulClientFactory(fhirContext);
hapiFhirCliRestfulClientFactory.setHttpClient(new Object());
} catch (UnsupportedOperationException e){
assertEquals(Msg.code(2119), e.getMessage());
}
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testSetProxy(FhirVersionEnum theFhirVersion){
try {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
FhirContext fhirContext = fhirVersionParams.getFhirContext();
HapiFhirCliRestfulClientFactory hapiFhirCliRestfulClientFactory = new HapiFhirCliRestfulClientFactory(fhirContext);
hapiFhirCliRestfulClientFactory.setProxy("proxy", 1);
} catch (UnsupportedOperationException e){
assertEquals(Msg.code(2120), e.getMessage());
}
}
}

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId> <artifactId>hapi-fhir-cli</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath> <relativePath>../../hapi-deployable-pom</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
@ -57,6 +57,12 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r4</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- Unit test dependencies --> <!-- Unit test dependencies -->
<dependency> <dependency>

View File

@ -27,12 +27,15 @@ import java.util.concurrent.TimeUnit;
*/ */
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.api.Header; import ca.uhn.fhir.rest.client.api.Header;
import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; import ca.uhn.fhir.rest.client.impl.RestfulClientFactory;
import ca.uhn.fhir.tls.TlsAuthentication;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import java.util.Optional;
/** /**
* A Restful client factory based on OkHttp. * A Restful client factory based on OkHttp.
@ -53,7 +56,7 @@ public class OkHttpRestfulClientFactory extends RestfulClientFactory {
@Override @Override
protected IHttpClient getHttpClient(String theServerBase) { protected IHttpClient getHttpClient(String theServerBase) {
return new OkHttpRestfulClient(getNativeClient(), new StringBuilder(theServerBase), null, null, null, null); return getHttpClient(new StringBuilder(theServerBase), null, null, null, null);
} }
@Override @Override

View File

@ -1,16 +1,30 @@
package ca.uhn.fhir.okhttp; package ca.uhn.fhir.okhttp;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory;
import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import javax.net.ssl.SSLHandshakeException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class OkHttpRestfulClientFactoryTest { public class OkHttpRestfulClientFactoryTest extends BaseFhirVersionParameterizedTest {
private OkHttpRestfulClientFactory clientFactory; private OkHttpRestfulClientFactory clientFactory;
@ -46,14 +60,77 @@ public class OkHttpRestfulClientFactoryTest {
public void testSocketTimeout() { public void testSocketTimeout() {
clientFactory.setSocketTimeout(1515); clientFactory.setSocketTimeout(1515);
assertEquals(1515, ((OkHttpClient)clientFactory.getNativeClient()).readTimeoutMillis()); assertEquals(1515, ((OkHttpClient) clientFactory.getNativeClient()).readTimeoutMillis());
assertEquals(1515, ((OkHttpClient)clientFactory.getNativeClient()).writeTimeoutMillis()); assertEquals(1515, ((OkHttpClient) clientFactory.getNativeClient()).writeTimeoutMillis());
} }
@Test @Test
public void testConnectTimeout() { public void testConnectTimeout() {
clientFactory.setConnectTimeout(1516); clientFactory.setConnectTimeout(1516);
assertEquals(1516, ((OkHttpClient)clientFactory.getNativeClient()).connectTimeoutMillis()); assertEquals(1516, ((OkHttpClient) clientFactory.getNativeClient()).connectTimeoutMillis());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testNativeClientHttp(FhirVersionEnum theFhirVersion) throws Exception {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
OkHttpRestfulClientFactory clientFactory = new OkHttpRestfulClientFactory(fhirVersionParams.getFhirContext());
OkHttpClient client = (OkHttpClient) clientFactory.getNativeClient();
Request request = new Request.Builder()
.url(fhirVersionParams.getPatientEndpoint())
.build();
Response response = client.newCall(request).execute();
assertEquals(200, response.code());
String json = response.body().string();
IBaseResource bundle = fhirVersionParams.getFhirContext().newJsonParser().parseResource(json);
assertEquals(fhirVersionParams.getFhirVersion(), bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testNativeClientHttpsNoCredentials(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
OkHttpRestfulClientFactory clientFactory = new OkHttpRestfulClientFactory(fhirVersionParams.getFhirContext());
OkHttpClient unauthenticatedClient = (OkHttpClient) clientFactory.getNativeClient();
try {
Request request = new Request.Builder()
.url(fhirVersionParams.getSecuredPatientEndpoint())
.build();
unauthenticatedClient.newCall(request).execute();
fail();
} catch (Exception e) {
assertEquals(SSLHandshakeException.class, e.getClass());
}
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testGenericClientHttp(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
String base = fhirVersionParams.getBase();
FhirContext context = fhirVersionParams.getFhirContext();
context.setRestfulClientFactory(new OkHttpRestfulClientFactory(context));
IBaseResource bundle = context.newRestfulGenericClient(base).search().forResource("Patient").execute();
assertEquals(theFhirVersion, bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testGenericClientHttpsNoCredentials(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
String secureBase = fhirVersionParams.getSecureBase();
FhirContext context = fhirVersionParams.getFhirContext();
context.setRestfulClientFactory(new OkHttpRestfulClientFactory(context));
try {
context.newRestfulGenericClient(secureBase).search().forResource("Patient").execute();
fail();
} catch (Exception e) {
assertTrue(e.getMessage().contains("HAPI-1357: Failed to retrieve the server metadata statement during client initialization"));
assertEquals(SSLHandshakeException.class, e.getCause().getCause().getClass());
}
} }
} }

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -20,11 +20,11 @@ package ca.uhn.fhir.rest.client.apache;
* #L% * #L%
*/ */
import java.util.List; import ca.uhn.fhir.context.FhirContext;
import java.util.Map; import ca.uhn.fhir.rest.api.RequestTypeEnum;
import java.util.concurrent.TimeUnit; import ca.uhn.fhir.rest.client.api.Header;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.rest.client.impl.RestfulClientFactory;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope; import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.auth.UsernamePasswordCredentials;
@ -37,11 +37,11 @@ import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.ProxyAuthenticationStrategy; import org.apache.http.impl.client.ProxyAuthenticationStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import ca.uhn.fhir.context.FhirContext; import java.util.List;
import ca.uhn.fhir.rest.api.RequestTypeEnum; import java.util.Map;
import ca.uhn.fhir.rest.client.api.Header; import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; import static org.apache.commons.lang3.StringUtils.isNotBlank;
/** /**
* A Restful Factory to create clients, requests and responses based on the Apache httpclient library. * A Restful Factory to create clients, requests and responses based on the Apache httpclient library.
@ -71,25 +71,19 @@ public class ApacheRestfulClientFactory extends RestfulClientFactory {
} }
@Override @Override
protected synchronized ApacheHttpClient getHttpClient(String theServerBase) { protected synchronized IHttpClient getHttpClient(String theServerBase) {
return new ApacheHttpClient(getNativeHttpClient(), new StringBuilder(theServerBase), null, null, null, null); return getHttpClient(new StringBuilder(theServerBase), null, null, null, null);
} }
@Override @Override
public synchronized IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams, public synchronized IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams,
String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) { String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) {
return new ApacheHttpClient(getNativeHttpClient(), theUrl, theIfNoneExistParams, theIfNoneExistString, theRequestType, return new ApacheHttpClient(getNativeHttpClient(), theUrl, theIfNoneExistParams, theIfNoneExistString, theRequestType, theHeaders);
theHeaders);
} }
public HttpClient getNativeHttpClient() { public HttpClient getNativeHttpClient() {
if (myHttpClient == null) { if (myHttpClient == null) {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000,
TimeUnit.MILLISECONDS);
connectionManager.setMaxTotal(getPoolMaxTotal());
connectionManager.setDefaultMaxPerRoute(getPoolMaxPerRoute());
//TODO: Use of a deprecated method should be resolved. //TODO: Use of a deprecated method should be resolved.
RequestConfig defaultRequestConfig = RequestConfig defaultRequestConfig =
RequestConfig.custom() RequestConfig.custom()
@ -102,11 +96,15 @@ public class ApacheRestfulClientFactory extends RestfulClientFactory {
HttpClientBuilder builder = getHttpClientBuilder() HttpClientBuilder builder = getHttpClientBuilder()
.useSystemProperties() .useSystemProperties()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(defaultRequestConfig) .setDefaultRequestConfig(defaultRequestConfig)
.disableCookieManagement(); .disableCookieManagement();
if (myProxy != null && StringUtils.isNotBlank(getProxyUsername()) && StringUtils.isNotBlank(getProxyPassword())) { PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
connectionManager.setMaxTotal(getPoolMaxTotal());
connectionManager.setDefaultMaxPerRoute(getPoolMaxPerRoute());
builder.setConnectionManager(connectionManager);
if (myProxy != null && isNotBlank(getProxyUsername()) && isNotBlank(getProxyPassword())) {
CredentialsProvider credsProvider = new BasicCredentialsProvider(); CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(myProxy.getHostName(), myProxy.getPort()), credsProvider.setCredentials(new AuthScope(myProxy.getHostName(), myProxy.getPort()),
new UsernamePasswordCredentials(getProxyUsername(), getProxyPassword())); new UsernamePasswordCredentials(getProxyUsername(), getProxyPassword()));

View File

@ -19,25 +19,35 @@ package ca.uhn.fhir.rest.client.impl;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import ca.uhn.fhir.i18n.Msg;
import java.lang.reflect.*;
import java.util.*;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.FhirClientInappropriateForServerException;
import ca.uhn.fhir.rest.client.method.BaseMethodBinding;
import ca.uhn.fhir.util.FhirTerser;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.*; import java.lang.reflect.InvocationHandler;
import ca.uhn.fhir.parser.DataFormatException; import java.lang.reflect.Method;
import ca.uhn.fhir.rest.api.Constants; import java.lang.reflect.Proxy;
import ca.uhn.fhir.rest.client.api.*; import java.util.Collections;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; import java.util.HashMap;
import ca.uhn.fhir.rest.client.exceptions.FhirClientInappropriateForServerException; import java.util.HashSet;
import ca.uhn.fhir.rest.client.method.BaseMethodBinding; import java.util.Map;
import ca.uhn.fhir.util.FhirTerser; import java.util.Set;
import javax.annotation.concurrent.GuardedBy;
/** /**
* Base class for a REST client factory implementation * Base class for a REST client factory implementation

View File

@ -0,0 +1,130 @@
package ca.uhn.fhir.rest.client.tls;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.tls.KeyStoreInfo;
import ca.uhn.fhir.tls.PathType;
import ca.uhn.fhir.tls.StoreInfo;
import ca.uhn.fhir.tls.TlsAuthentication;
import ca.uhn.fhir.tls.TrustStoreInfo;
import org.apache.commons.lang3.Validate;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.ssl.PrivateKeyStrategy;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import javax.annotation.Nonnull;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class TlsAuthenticationSvc {
private TlsAuthenticationSvc(){}
public static SSLContext createSslContext(@Nonnull TlsAuthentication theTlsAuthentication){
Validate.notNull(theTlsAuthentication, "theTlsAuthentication cannot be null");
try{
SSLContextBuilder contextBuilder = SSLContexts.custom();
if(theTlsAuthentication.getKeyStoreInfo().isPresent()){
KeyStoreInfo keyStoreInfo = theTlsAuthentication.getKeyStoreInfo().get();
PrivateKeyStrategy privateKeyStrategy = null;
if(isNotBlank(keyStoreInfo.getAlias())){
privateKeyStrategy = (aliases, socket) -> keyStoreInfo.getAlias();
}
KeyStore keyStore = createKeyStore(keyStoreInfo);
contextBuilder.loadKeyMaterial(keyStore, keyStoreInfo.getKeyPass(), privateKeyStrategy);
}
if(theTlsAuthentication.getTrustStoreInfo().isPresent()){
TrustStoreInfo trustStoreInfo = theTlsAuthentication.getTrustStoreInfo().get();
KeyStore trustStore = createKeyStore(trustStoreInfo);
contextBuilder.loadTrustMaterial(trustStore, TrustSelfSignedStrategy.INSTANCE);
}
return contextBuilder.build();
}
catch (Exception e){
throw new TlsAuthenticationException(Msg.code(2102)+"Failed to create SSLContext", e);
}
}
public static KeyStore createKeyStore(StoreInfo theStoreInfo){
try {
KeyStore keyStore = KeyStore.getInstance(theStoreInfo.getType().toString());
if(PathType.RESOURCE.equals(theStoreInfo.getPathType())){
try(InputStream inputStream = TlsAuthenticationSvc.class.getResourceAsStream(theStoreInfo.getFilePath())){
validateKeyStoreExists(inputStream);
keyStore.load(inputStream, theStoreInfo.getStorePass());
}
}
else if(PathType.FILE.equals(theStoreInfo.getPathType())){
try(InputStream inputStream = new FileInputStream(theStoreInfo.getFilePath())){
validateKeyStoreExists(inputStream);
keyStore.load(inputStream, theStoreInfo.getStorePass());
}
}
return keyStore;
}
catch (Exception e){
throw new TlsAuthenticationException(Msg.code(2103)+"Failed to create KeyStore", e);
}
}
public static void validateKeyStoreExists(InputStream theInputStream){
if(theInputStream == null){
throw new TlsAuthenticationException(Msg.code(2116)+"Keystore does not exists");
}
}
public static X509TrustManager createTrustManager(Optional<TrustStoreInfo> theTrustStoreInfo) {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
if (theTrustStoreInfo.isEmpty()) {
trustManagerFactory.init((KeyStore) null); // Load Trust Manager Factory with default Java truststore
}
else {
TrustStoreInfo trustStoreInfo = theTrustStoreInfo.get();
KeyStore trustStore = createKeyStore(trustStoreInfo);
trustManagerFactory.init(trustStore);
}
for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
return (X509TrustManager) trustManager;
}
}
throw new TlsAuthenticationException(Msg.code(2104)+"Could not find X509TrustManager");
}
catch (Exception e) {
throw new TlsAuthenticationException(Msg.code(2105)+"Failed to create X509TrustManager");
}
}
public static HostnameVerifier createHostnameVerifier(Optional<TrustStoreInfo> theTrustStoreInfo){
return theTrustStoreInfo.isPresent() ? new DefaultHostnameVerifier() : new NoopHostnameVerifier();
}
public static class TlsAuthenticationException extends RuntimeException {
private static final long serialVersionUID = 1l;
public TlsAuthenticationException(String theMessage, Throwable theCause) {
super(theMessage, theCause);
}
public TlsAuthenticationException(String theMessage) {
super(theMessage);
}
}
}

View File

@ -0,0 +1,79 @@
package ca.uhn.fhir.rest.client.tls;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.tls.KeyStoreInfo;
import ca.uhn.fhir.tls.KeyStoreType;
import ca.uhn.fhir.tls.PathType;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class StoreInfoTest {
@Test
public void testPathTypeFile(){
KeyStoreInfo keyStoreInfo = new KeyStoreInfo("file:///my-file.p12", "storePassword" , "keyPassword", "alias");
assertEquals("/my-file.p12", keyStoreInfo.getFilePath());
assertEquals(PathType.FILE, keyStoreInfo.getPathType());
}
@Test
public void testPathTypeResource(){
KeyStoreInfo keyStoreInfo = new KeyStoreInfo("classpath:/my-file.p12", "storePassword" , "keyPassword", "alias");
assertEquals("/my-file.p12", keyStoreInfo.getFilePath());
assertEquals(PathType.RESOURCE, keyStoreInfo.getPathType());
}
@Test
public void testPathTypeInvalid(){
try {
new KeyStoreInfo("invalid:///my-file.p12", "storePassword" , "keyPassword", "alias");
fail();
} catch (Exception e) {
assertEquals(Msg.code(2117)+"Invalid path prefix", e.getMessage());
}
}
@Test
public void testFileTypeP12(){
KeyStoreInfo keyStoreInfo = new KeyStoreInfo("file:///my-file.p12", "storePassword" , "keyPassword", "alias");
assertEquals(KeyStoreType.PKCS12, keyStoreInfo.getType());
}
@Test
public void testFileTypeJks(){
KeyStoreInfo keyStoreInfo = new KeyStoreInfo("file:///my-file.jks", "storePassword" , "keyPassword", "alias");
assertEquals(KeyStoreType.JKS, keyStoreInfo.getType());
}
@Test
public void testFileTypeInvalid(){
try {
new KeyStoreInfo("file:///my-file.invalid", "storePassword" , "keyPassword", "alias");
fail();
} catch (Exception e) {
assertEquals(Msg.code(2121)+"Invalid KeyStore Type", e.getMessage());
}
}
@Test
public void testStorePass(){
KeyStoreInfo keyStoreInfo = new KeyStoreInfo("file:///my-file.p12", "storePassword" , "keyPassword", "alias");
assertTrue(StringUtils.equals("storePassword", new String(keyStoreInfo.getStorePass())));
}
@Test
public void testKeyPass(){
KeyStoreInfo keyStoreInfo = new KeyStoreInfo("file:///my-file.p12", "storePassword" , "keyPassword", "alias");
assertTrue(StringUtils.equals("keyPassword", new String(keyStoreInfo.getKeyPass())));
}
@Test
public void testAlias(){
KeyStoreInfo keyStoreInfo = new KeyStoreInfo("file:///my-file.p12", "storePassword" , "keyPassword", "alias");
assertEquals("alias", keyStoreInfo.getAlias());
}
}

View File

@ -0,0 +1,164 @@
package ca.uhn.fhir.rest.client.tls;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.tls.KeyStoreInfo;
import ca.uhn.fhir.tls.TlsAuthentication;
import ca.uhn.fhir.tls.TrustStoreInfo;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
public class TlsAuthenticationSvcTest {
private KeyStoreInfo myServerKeyStoreInfo;
private TrustStoreInfo myServerTrustStoreInfo;
private TlsAuthentication myServerTlsAuthentication;
private KeyStoreInfo myClientKeyStoreInfo;
private TrustStoreInfo myClientTrustStoreInfo;
private TlsAuthentication myClientTlsAuthentication;
@BeforeEach
public void beforeEach(){
myServerKeyStoreInfo = new KeyStoreInfo("classpath:/server-keystore.p12", "changeit", "changeit", "server");
myServerTrustStoreInfo = new TrustStoreInfo("classpath:/server-truststore.p12", "changeit", "client");
myServerTlsAuthentication = new TlsAuthentication(Optional.of(myServerKeyStoreInfo), Optional.of(myServerTrustStoreInfo));
myClientKeyStoreInfo = new KeyStoreInfo("classpath:/client-keystore.p12", "changeit", "changeit", "client");
myClientTrustStoreInfo = new TrustStoreInfo("classpath:/client-truststore.p12", "changeit", "server");
myClientTlsAuthentication = new TlsAuthentication(Optional.of(myClientKeyStoreInfo), Optional.of(myClientTrustStoreInfo));
}
@Test
public void testCreateSslContextEmpty(){
TlsAuthentication emptyAuthentication = null;
try {
TlsAuthenticationSvc.createSslContext(emptyAuthentication);
fail();
} catch (Exception e) {
assertEquals("theTlsAuthentication cannot be null", e.getMessage());
}
}
@Test
public void testCreateSslContextPresent(){
SSLContext result = TlsAuthenticationSvc.createSslContext(myServerTlsAuthentication);
assertEquals("TLS", result.getProtocol());
}
@Test
public void testCreateSslContextPresentInvalid(){
KeyStoreInfo invalidKeyStoreInfo = new KeyStoreInfo("file:///INVALID.p12", "changeit", "changeit", "server");
TlsAuthentication invalidTlsAuthentication = new TlsAuthentication(Optional.of(invalidKeyStoreInfo), Optional.of(myServerTrustStoreInfo));
try {
TlsAuthenticationSvc.createSslContext(invalidTlsAuthentication);
fail();
} catch (Exception e) {
assertEquals(Msg.code(2102)+"Failed to create SSLContext", e.getMessage());
}
}
@Test
public void testCreateKeyStoreP12() throws Exception {
KeyStore keyStore = TlsAuthenticationSvc.createKeyStore(myServerKeyStoreInfo);
assertNotNull(keyStore.getKey(myServerKeyStoreInfo.getAlias(), myServerKeyStoreInfo.getKeyPass()));
}
@Test
public void testCreateKeyStoreJKS() throws Exception {
KeyStoreInfo keyStoreInfo = new KeyStoreInfo("classpath:/keystore.jks", "changeit", "changeit", "client");
KeyStore keyStore = TlsAuthenticationSvc.createKeyStore(keyStoreInfo);
assertNotNull(keyStore.getKey(keyStoreInfo.getAlias(), keyStoreInfo.getKeyPass()));
}
@Test
public void testCreateKeyStoreNonExistentFile() {
KeyStoreInfo keyStoreInfo = new KeyStoreInfo("classpath:/non-existent.p12", "changeit", "changeit", "server");
try {
TlsAuthenticationSvc.createKeyStore(keyStoreInfo);
fail();
}
catch (Exception e) {
assertEquals(Msg.code(2103)+"Failed to create KeyStore", e.getMessage());
}
}
@Test
public void testCreateTrustStoreJks() throws Exception {
TrustStoreInfo trustStoreInfo = new TrustStoreInfo("classpath:/truststore.jks", "changeit", "client");
KeyStore keyStore = TlsAuthenticationSvc.createKeyStore(trustStoreInfo);
assertNotNull(keyStore.getCertificate(trustStoreInfo.getAlias()));
}
@Test
public void testCreateTrustStoreP12() throws Exception {
KeyStore keyStore = TlsAuthenticationSvc.createKeyStore(myServerTrustStoreInfo);
assertNotNull(keyStore.getCertificate(myServerTrustStoreInfo.getAlias()));
}
@Test
public void testCreateTrustStoreNonExistentFile() throws Exception {
TrustStoreInfo trustStoreInfo = new TrustStoreInfo("classpath:/non-existent.p12", "changeit", "server");
try {
TlsAuthenticationSvc.createKeyStore(trustStoreInfo);
fail();
} catch (Exception e) {
assertEquals(Msg.code(2103)+"Failed to create KeyStore", e.getMessage());
}
}
@Test
public void testCreateTrustManager() throws Exception {
X509TrustManager trustManager = TlsAuthenticationSvc.createTrustManager(Optional.of(myClientTrustStoreInfo));
KeyStore keyStore = TlsAuthenticationSvc.createKeyStore(myServerKeyStoreInfo);
Certificate serverCertificate = keyStore.getCertificate(myServerKeyStoreInfo.getAlias());
assertEquals(1, trustManager.getAcceptedIssuers().length);
assertEquals(serverCertificate, trustManager.getAcceptedIssuers()[0]);
}
@Test
public void testCreateTrustManagerNoTrustStore() {
// trust manager should contain common certifications if no trust store information is used
X509TrustManager trustManager = TlsAuthenticationSvc.createTrustManager(Optional.empty());
assertNotEquals(0, trustManager.getAcceptedIssuers().length);
}
@Test
public void testCreateTrustManagerInvalid() {
TrustStoreInfo invalidKeyStoreInfo = new TrustStoreInfo("file:///INVALID.p12", "changeit", "client");
try {
TlsAuthenticationSvc.createTrustManager(Optional.of(invalidKeyStoreInfo));
fail();
} catch (Exception e) {
assertEquals(Msg.code(2105)+"Failed to create X509TrustManager", e.getMessage());
}
}
@Test
public void testCreateHostnameVerifierEmptyTrustStoreInfo(){
Optional<TrustStoreInfo> trustStoreInfo = Optional.empty();
HostnameVerifier result = TlsAuthenticationSvc.createHostnameVerifier(trustStoreInfo);
assertEquals(NoopHostnameVerifier.class, result.getClass());
}
@Test
public void testCreateHostnameVerifierPresentTrustStoreInfo(){
Optional<TrustStoreInfo> trustStoreInfo = Optional.of(myServerTrustStoreInfo);
HostnameVerifier result = TlsAuthenticationSvc.createHostnameVerifier(trustStoreInfo);
assertEquals(DefaultHostnameVerifier.class, result.getClass());
}
}

Binary file not shown.

Binary file not shown.

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -0,0 +1,8 @@
---
type: add
issue: 3776
jira: SMILE-651
title: "Previously, the HAPI FHIR CLI commands that made HTTP requests could only be used for HTTP endpoints.
This feature adds support for HTTPS endpoints by using TLS authentication for `ExampleDataUploader`,
`ExportConceptMapToCsvCommand`, `ImportCsvToConceptMapCommand`, `ReindexTerminologyCommand` and
`UploadTerminologyCommand`."

View File

@ -11,7 +11,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -1,12 +1,5 @@
package ca.uhn.fhir.jaxrs.client; package ca.uhn.fhir.jaxrs.client;
import ca.uhn.fhir.i18n.Msg;
import java.util.List;
import java.util.Map;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
/* /*
* #%L * #%L
* HAPI FHIR JAX-RS Server * HAPI FHIR JAX-RS Server
@ -28,10 +21,20 @@ import javax.ws.rs.client.ClientBuilder;
*/ */
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.api.Header; import ca.uhn.fhir.rest.client.api.Header;
import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; import ca.uhn.fhir.rest.client.impl.RestfulClientFactory;
import ca.uhn.fhir.rest.client.tls.TlsAuthenticationSvc;
import ca.uhn.fhir.tls.TlsAuthentication;
import javax.net.ssl.SSLContext;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/** /**
* A Restful Client Factory, based on Jax Rs * A Restful Client Factory, based on Jax Rs

View File

@ -1,29 +1,44 @@
package ca.uhn.fhir.jaxrs.client; package ca.uhn.fhir.jaxrs.client;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest;
import com.google.common.net.MediaType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLHandshakeException;
import javax.ws.rs.client.Client; import javax.ws.rs.client.Client;
import javax.ws.rs.core.Response;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNot.not;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
/** /**
* Created by Sebastien Riviere on 31/07/2017. * Created by Sebastien Riviere on 31/07/2017.
*/ */
public class JaxRsRestfulClientFactoryTest { public class JaxRsRestfulClientFactoryTest extends BaseFhirVersionParameterizedTest {
private static final Logger ourLog = LoggerFactory.getLogger(JaxRsRestfulClientFactoryTest.class); private static final Logger ourLog = LoggerFactory.getLogger(JaxRsRestfulClientFactoryTest.class);
private final FhirContext context = FhirContext.forDstu2(); private final FhirContext context = FhirContext.forDstu2();
private JaxRsRestfulClientFactory factory; private JaxRsRestfulClientFactory factory;
@BeforeEach
public void beforeEach() throws Exception {
factory = new JaxRsRestfulClientFactory(context);
}
@Test @Test
public void emptyConstructorTest() { public void emptyConstructorTest() {
assertNotNull(new JaxRsRestfulClientFactory()); assertNotNull(new JaxRsRestfulClientFactory());
@ -52,8 +67,39 @@ public class JaxRsRestfulClientFactoryTest {
assertThat(result.getConfiguration().getClasses(), hasItem(ca.uhn.fhir.jaxrs.client.MyFilter.class)); assertThat(result.getConfiguration().getClasses(), hasItem(ca.uhn.fhir.jaxrs.client.MyFilter.class));
} }
@BeforeEach @ParameterizedTest
public void setUp() { @MethodSource("baseParamsProvider")
factory = new JaxRsRestfulClientFactory(context); public void testNativeClientHttp(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
JaxRsRestfulClientFactory factory = new JaxRsRestfulClientFactory(fhirVersionParams.getFhirContext());
Client client = factory.getNativeClientClient();
Response response = client
.target(fhirVersionParams.getPatientEndpoint())
.request(MediaType.JSON_UTF_8.toString())
.get(Response.class);
assertEquals(200, response.getStatus());
String json = response.readEntity(String.class);
IBaseResource bundle = fhirVersionParams.parseResource(json);
assertEquals(fhirVersionParams.getFhirVersion(), bundle.getStructureFhirVersionEnum());
}
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testNativeClientHttpsNoCredentials(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
JaxRsRestfulClientFactory factory = new JaxRsRestfulClientFactory(fhirVersionParams.getFhirContext());
Client unauthenticatedClient = factory.getNativeClientClient();
try {
unauthenticatedClient
.target(fhirVersionParams.getSecuredPatientEndpoint())
.request(MediaType.JSON_UTF_8.toString())
.get(Response.class);
fail();
} catch (Exception e) {
assertEquals(SSLHandshakeException.class, e.getCause().getClass());
}
} }
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId> <artifactId>hapi-fhir-spring-boot</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
</parent> </parent>
<artifactId>hapi-fhir-spring-boot-samples</artifactId> <artifactId>hapi-fhir-spring-boot-samples</artifactId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
@ -38,6 +38,12 @@
<version>${fhir_core_version}</version> <version>${fhir_core_version}</version>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.dstu3</artifactId>
<version>${fhir_core_version}</version>
</dependency>
<!-- General --> <!-- General -->
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
@ -173,6 +179,15 @@
<argLine>@{argLine} ${surefire_jvm_args}</argLine> <argLine>@{argLine} ${surefire_jvm_args}</argLine>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>p12</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins> </plugins>
<resources> <resources>
<resource> <resource>

View File

@ -0,0 +1,92 @@
package ca.uhn.fhir.test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.tls.TlsAuthentication;
import ca.uhn.fhir.test.utilities.TlsAuthenticationTestHelper;
import ca.uhn.fhir.test.utilities.BaseRestServerHelper;
import ca.uhn.fhir.test.utilities.RestServerDstu3Helper;
import ca.uhn.fhir.test.utilities.RestServerR4Helper;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.provider.Arguments;
import java.util.stream.Stream;
public class BaseFhirVersionParameterizedTest {
@RegisterExtension
public final RestServerR4Helper myRestServerR4Helper = new RestServerR4Helper(true);
@RegisterExtension
public final RestServerDstu3Helper myRestServerDstu3Helper = new RestServerDstu3Helper(true);
@RegisterExtension
public TlsAuthenticationTestHelper myTlsAuthenticationTestHelper = new TlsAuthenticationTestHelper();
protected final FhirContext myR4FhirContext = FhirContext.forR4();
protected final FhirContext myDstu3FhirContext = FhirContext.forDstu3();
protected static Stream<Arguments> baseParamsProvider(){
return Stream.of(
Arguments.arguments(FhirVersionEnum.R4),
Arguments.arguments(FhirVersionEnum.DSTU3)
);
}
protected FhirVersionParams getFhirVersionParams(FhirVersionEnum theFhirVersion){
switch(theFhirVersion){
case R4:
return new FhirVersionParams(myRestServerR4Helper, myR4FhirContext);
case DSTU3:
return new FhirVersionParams(myRestServerDstu3Helper, myDstu3FhirContext);
default:
throw new RuntimeException(Msg.code(2114)+"Unknown FHIR Version param provided: " + theFhirVersion);
}
}
protected TlsAuthentication getTlsAuthentication(){
return myTlsAuthenticationTestHelper.getTlsAuthentication();
}
protected class FhirVersionParams {
private final BaseRestServerHelper myBaseRestServerHelper;
private final FhirContext myFhirContext;
private final FhirVersionEnum myFhirVersion;
public FhirVersionParams(BaseRestServerHelper theBaseRestServerHelper, FhirContext theFhirContext) {
myBaseRestServerHelper = theBaseRestServerHelper;
myFhirContext = theFhirContext;
myFhirVersion = theFhirContext.getVersion().getVersion();
}
public FhirContext getFhirContext() {
return myFhirContext;
}
public FhirVersionEnum getFhirVersion() {
return myFhirVersion;
}
public String getBase(){
return myBaseRestServerHelper.getBase();
}
public String getSecureBase(){
return myBaseRestServerHelper.getSecureBase();
}
public String getPatientEndpoint(){
return getBase()+"/Patient";
}
public String getSecuredPatientEndpoint(){
return getSecureBase()+"/Patient";
}
public IBaseResource parseResource(String json){
return myFhirContext.newJsonParser().parseResource(json);
}
}
}

View File

@ -0,0 +1,204 @@
package ca.uhn.fhir.test.utilities;
/*-
* #%L
* HAPI FHIR Test Utilities
* %%
* Copyright (C) 2014 - 2022 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.i18n.Msg;
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.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.IServerAddressStrategy;
import ca.uhn.fhir.tls.KeyStoreType;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.extension.RegisterExtension;
import javax.servlet.Servlet;
import java.security.KeyStore;
import java.util.List;
public abstract class BaseRestServerHelper {
private final String SERVER_KEYSTORE_PATH = "/tls/server-keystore.p12";
private final String SERVER_TRUSTSTORE_PATH = "/tls/server-truststore.p12";
private final String PASSWORD = "changeit";
protected final FhirContext myFhirContext;
protected int myListenerPort;
protected int myHttpsListenerPort;
protected Server myListenerServer;
protected String myBase;
protected String mySecureBase;
protected IGenericClient myClient;
@RegisterExtension
public TlsAuthenticationTestHelper myTlsAuthenticationTestHelper = new TlsAuthenticationTestHelper();
public BaseRestServerHelper(FhirContext theFhirContext) {
myFhirContext = theFhirContext;
}
protected void afterEach() throws Exception {
stop();
}
public IGenericClient getClient() {
return myClient;
}
protected void startServer(Servlet theServlet) throws Exception {
myListenerServer = new Server(0);
myFhirContext.getRestfulClientFactory().setSocketTimeout(120000);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
ServletHolder targetServletHolder = new ServletHolder();
targetServletHolder.setServlet(theServlet);
proxyHandler.addServlet(targetServletHolder, "/target/*");
myListenerServer.setHandler(proxyHandler);
SslContextFactory sslContextFactory = getSslContextFactory();
HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.setSecureScheme("https");
httpsConfig.setSecurePort(0);
ServerConnector sslConnector = new ServerConnector(myListenerServer,
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(httpsConfig));
sslConnector.setPort(0);
myListenerServer.addConnector(sslConnector);
myListenerServer.start();
assignHttpAndHttpsPorts();
myBase = "http://localhost:" + myListenerPort + "/target";
mySecureBase = "https://localhost:" + myHttpsListenerPort + "/target";
myFhirContext.getRestfulClientFactory().setConnectTimeout(60000);
myFhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
myClient = myFhirContext.newRestfulGenericClient(myBase);
myClient.registerInterceptor(new LoggingInterceptor(false));
}
private void assignHttpAndHttpsPorts() {
myListenerPort = ((ServerConnector)myListenerServer.getConnectors()[0]).getLocalPort();
myHttpsListenerPort = ((ServerConnector)myListenerServer.getConnectors()[1]).getLocalPort();
}
private SslContextFactory getSslContextFactory() throws Exception{
try {
SslContextFactory sslContextFactory = new SslContextFactory.Server();
KeyStore keyStore = KeyStore.getInstance(KeyStoreType.PKCS12.toString());
keyStore.load(BaseRestServerHelper.class.getResourceAsStream(SERVER_KEYSTORE_PATH), PASSWORD.toCharArray());
sslContextFactory.setKeyStore(keyStore);
sslContextFactory.setKeyStorePassword(PASSWORD);
KeyStore trustStore = KeyStore.getInstance(KeyStoreType.PKCS12.toString());
trustStore.load(BaseRestServerHelper.class.getResourceAsStream(SERVER_TRUSTSTORE_PATH), PASSWORD.toCharArray());
sslContextFactory.setTrustStore(trustStore);
return sslContextFactory;
}
catch(Exception e){
throw new RuntimeException(Msg.code(2123)+"Failed to obtain SslContextFactory", e);
}
}
public String getBase() {
return myBase;
}
public String getSecureBase() {
return mySecureBase;
}
public void stop() throws Exception {
JettyUtil.closeServer(myListenerServer);
}
public abstract void clearDataAndCounts();
public abstract void setFailNextPut(boolean theFailNextPut);
public abstract List<Object> getInterceptors();
public abstract void unregisterInterceptor(Object theNext);
public abstract void clearCounts();
public abstract long getPatientCountSearch();
public abstract long getPatientCountDelete();
public abstract long getPatientCountUpdate();
public abstract long getPatientCountRead();
public abstract long getObservationCountSearch();
public abstract long getObservationCountDelete();
public abstract long getObservationCountUpdate();
public abstract long getObservationCountRead();
public abstract boolean registerInterceptor(Object theInterceptorAdapter);
public abstract IResourceProvider getObservationResourceProvider();
public abstract IResourceProvider getPatientResourceProvider();
public abstract IResourceProvider getConceptMapResourceProvider();
public abstract IIdType createPatientWithId(String theId);
public abstract IIdType createPatient(IBaseResource theBaseResource);
public abstract IIdType createObservationForPatient(IIdType theFirstTargetPatientId);
public abstract IIdType createObservation(IBaseResource theBaseResource);
public void setServerAddressStrategy(boolean theUseHttps){
String path = theUseHttps ? mySecureBase : myBase;
HardcodedServerAddressStrategy strategy = new HardcodedServerAddressStrategy(path);
setServerAddressStrategy(strategy);
}
protected abstract void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy);
}

View File

@ -0,0 +1,354 @@
package ca.uhn.fhir.test.utilities;
/*-
* #%L
* HAPI FHIR Test Utilities
* %%
* Copyright (C) 2014 - 2022 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.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.rest.annotation.Transaction;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.IServerAddressStrategy;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
import ca.uhn.test.concurrency.IPointcutLatch;
import ca.uhn.test.concurrency.PointcutLatch;
import org.hl7.fhir.dstu3.model.ConceptMap;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Organization;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import javax.servlet.ServletException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class RestServerDstu3Helper extends BaseRestServerHelper implements IPointcutLatch, BeforeEachCallback, AfterEachCallback {
protected final MyRestfulServer myRestServer;
public RestServerDstu3Helper() {
this(false);
}
public RestServerDstu3Helper(boolean theInitialize) {
super(FhirContext.forDstu3());
myRestServer = new MyRestfulServer(myFhirContext);
if(theInitialize){
try {
myRestServer.initialize();
} catch (ServletException e) {
throw new RuntimeException(Msg.code(2112)+"Failed to initialize server", e);
}
}
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
startServer(myRestServer);
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
super.afterEach();
myRestServer.getInterceptorService().unregisterAllAnonymousInterceptors();
myRestServer.clearDataAndCounts();
}
@Override
public void clearDataAndCounts() {
myRestServer.clearDataAndCounts();
}
@Override
public void setFailNextPut(boolean theFailNextPut) {
myRestServer.setFailNextPut(theFailNextPut);
}
@Override
public List<Object> getInterceptors() {
return myRestServer.getInterceptorService().getAllRegisteredInterceptors();
}
@Override
public void unregisterInterceptor(Object theInterceptor) {
myRestServer.getInterceptorService().unregisterInterceptor(theInterceptor);
}
@Override
public void clearCounts() {
myRestServer.clearCounts();
}
@Override
public long getPatientCountSearch() {
return myRestServer.getPatientResourceProvider().getCountSearch();
}
@Override
public long getPatientCountDelete() {
return myRestServer.getPatientResourceProvider().getCountDelete();
}
@Override
public long getPatientCountUpdate() {
return myRestServer.getPatientResourceProvider().getCountUpdate();
}
@Override
public long getPatientCountRead() {
return myRestServer.getPatientResourceProvider().getCountRead();
}
@Override
public long getObservationCountSearch() {
return myRestServer.getObservationResourceProvider().getCountSearch();
}
@Override
public long getObservationCountDelete() {
return myRestServer.getObservationResourceProvider().getCountDelete();
}
@Override
public long getObservationCountUpdate() {
return myRestServer.getObservationResourceProvider().getCountUpdate();
}
@Override
public long getObservationCountRead() {
return myRestServer.getObservationResourceProvider().getCountRead();
}
@Override
public boolean registerInterceptor(Object theInterceptor) {
return myRestServer.getInterceptorService().registerInterceptor(theInterceptor);
}
@Override
public void clear() {
myRestServer.getPlainProvider().clear();
}
@Override
public void setExpectedCount(int theCount) {
myRestServer.getPlainProvider().setExpectedCount(theCount);
}
@Override
public List<HookParams> awaitExpected() throws InterruptedException {
return myRestServer.getPlainProvider().awaitExpected();
}
public void registerProvider(Object theProvider) {
myRestServer.registerProvider(theProvider);
}
public static class MyPlainProvider implements IPointcutLatch {
private final PointcutLatch myPointcutLatch = new PointcutLatch("Transaction Counting Provider");
private final List<IBaseBundle> myTransactions = Collections.synchronizedList(new ArrayList<>());
@Transaction
public synchronized IBaseBundle transaction(@TransactionParam IBaseBundle theBundle) {
myPointcutLatch.call(theBundle);
myTransactions.add(theBundle);
return theBundle;
}
@Override
public void clear() {
myPointcutLatch.clear();
}
@Override
public void setExpectedCount(int theCount) {
myPointcutLatch.setExpectedCount(theCount);
}
@Override
public List<HookParams> awaitExpected() throws InterruptedException {
return myPointcutLatch.awaitExpected();
}
public List<IBaseBundle> getTransactions() {
return Collections.unmodifiableList(new ArrayList<>(myTransactions));
}
}
private static class MyRestfulServer extends RestfulServer {
private boolean myFailNextPut;
private HashMapResourceProvider<Patient> myPatientResourceProvider;
private HashMapResourceProvider<Observation> myObservationResourceProvider;
private HashMapResourceProvider<Organization> myOrganizationResourceProvider;
private HashMapResourceProvider<ConceptMap> myConceptMapResourceProvider;
private MyPlainProvider myPlainProvider;
public MyRestfulServer(FhirContext theFhirContext) {
super(theFhirContext);
}
public MyPlainProvider getPlainProvider() {
return myPlainProvider;
}
public void setFailNextPut(boolean theFailNextPut) {
myFailNextPut = theFailNextPut;
}
public void clearCounts() {
for (IResourceProvider next : getResourceProviders()) {
if (next instanceof HashMapResourceProvider) {
HashMapResourceProvider provider = (HashMapResourceProvider) next;
provider.clearCounts();
}
}
myPlainProvider.clear();
}
public void clearDataAndCounts() {
for (IResourceProvider next : getResourceProviders()) {
if (next instanceof HashMapResourceProvider) {
HashMapResourceProvider provider = (HashMapResourceProvider) next;
provider.clear();
}
}
clearCounts();
}
public HashMapResourceProvider<Observation> getObservationResourceProvider() {
return myObservationResourceProvider;
}
public HashMapResourceProvider<Organization> getOrganizationResourceProvider() {
return myOrganizationResourceProvider;
}
public HashMapResourceProvider<ConceptMap> getConceptMapResourceProvider() {
return myConceptMapResourceProvider;
}
public HashMapResourceProvider<Patient> getPatientResourceProvider() {
return myPatientResourceProvider;
}
@Override
protected void initialize() throws ServletException {
super.initialize();
FhirContext fhirContext = getFhirContext();
myPatientResourceProvider = new MyHashMapResourceProvider(fhirContext, Patient.class);
registerProvider(myPatientResourceProvider);
myObservationResourceProvider = new MyHashMapResourceProvider(fhirContext, Observation.class);
registerProvider(myObservationResourceProvider);
myOrganizationResourceProvider = new MyHashMapResourceProvider(fhirContext, Organization.class);
registerProvider(myOrganizationResourceProvider);
myConceptMapResourceProvider = new MyHashMapResourceProvider(fhirContext, ConceptMap.class);
registerProvider(myConceptMapResourceProvider);
myPlainProvider = new MyPlainProvider();
registerProvider(myPlainProvider);
}
public void setConceptMapResourceProvider(HashMapResourceProvider<ConceptMap> theResourceProvider) {
myConceptMapResourceProvider.getStoredResources().forEach(c -> theResourceProvider.store(c));
unregisterProvider(myConceptMapResourceProvider);
registerProvider(theResourceProvider);
myConceptMapResourceProvider = theResourceProvider;
}
public class MyHashMapResourceProvider<T extends IBaseResource> extends HashMapResourceProvider<T> {
public MyHashMapResourceProvider(FhirContext theContext, Class theType) {
super(theContext, theType);
}
@Override
public MethodOutcome update(T theResource, String theConditional, RequestDetails theRequestDetails) {
if (myFailNextPut) {
throw new PreconditionFailedException(Msg.code(2113)+"Failed update operation");
}
return super.update(theResource, theConditional, theRequestDetails);
}
}
}
@Override
public HashMapResourceProvider<Observation> getObservationResourceProvider() {
return myRestServer.getObservationResourceProvider();
}
@Override
public HashMapResourceProvider<Patient> getPatientResourceProvider() {
return myRestServer.getPatientResourceProvider();
}
@Override
public HashMapResourceProvider<ConceptMap> getConceptMapResourceProvider() {
return myRestServer.getConceptMapResourceProvider();
}
@Override
public IIdType createPatientWithId(String theId) {
Patient patient = new Patient();
patient.setId("Patient/" + theId);
patient.addIdentifier().setSystem("http://foo").setValue(theId);
return this.createPatient(patient);
}
@Override
public IIdType createPatient(IBaseResource theBaseResource) {
return myRestServer.getPatientResourceProvider().store((Patient) theBaseResource);
}
@Override
public IIdType createObservationForPatient(IIdType theFirstTargetPatientId) {
Observation observation = new Observation();
observation.setSubject(new Reference(theFirstTargetPatientId));
return this.createObservation(observation);
}
@Override
public IIdType createObservation(IBaseResource theBaseResource) {
return myRestServer.getObservationResourceProvider().store((Observation) theBaseResource);
}
@Override
protected void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
myRestServer.setServerAddressStrategy(theServerAddressStrategy);
}
public void setConceptMapResourceProvider(HashMapResourceProvider<ConceptMap> theResourceProvider) {
myRestServer.setConceptMapResourceProvider(theResourceProvider);
}
}

View File

@ -0,0 +1,387 @@
package ca.uhn.fhir.test.utilities;
/*-
* #%L
* HAPI FHIR Test Utilities
* %%
* Copyright (C) 2014 - 2022 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.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.IServerAddressStrategy;
import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
import org.eclipse.jetty.server.Request;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class RestServerR4Helper extends BaseRestServerHelper implements BeforeEachCallback, AfterEachCallback {
protected final MyRestfulServer myRestServer;
public RestServerR4Helper() {
this(false);
}
public RestServerR4Helper(boolean theInitialize) {
super(FhirContext.forR4Cached());
myRestServer = new MyRestfulServer(myFhirContext);
if(theInitialize){
try {
myRestServer.initialize();
} catch (ServletException e) {
throw new RuntimeException(Msg.code(2110)+"Failed to initialize server", e);
}
}
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
startServer(myRestServer);
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
super.afterEach();
myRestServer.getInterceptorService().unregisterAllAnonymousInterceptors();
myRestServer.clearDataAndCounts();
}
public List<Bundle> getTransactions() {
return myRestServer
.getPlainProvider()
.getTransactions()
.stream()
.map(t -> (Bundle) t)
.collect(Collectors.toList());
}
@Override
public void clearDataAndCounts() {
myRestServer.clearDataAndCounts();
}
@Override
public void setFailNextPut(boolean theFailNextPut) {
myRestServer.setFailNextPut(theFailNextPut);
}
@Override
public List<Object> getInterceptors() {
return myRestServer.getInterceptorService().getAllRegisteredInterceptors();
}
@Override
public void unregisterInterceptor(Object theInterceptor) {
myRestServer.getInterceptorService().unregisterInterceptor(theInterceptor);
}
@Override
public void clearCounts() {
myRestServer.clearCounts();
}
@Override
public long getPatientCountSearch() {
return myRestServer.getPatientResourceProvider().getCountSearch();
}
@Override
public long getPatientCountDelete() {
return myRestServer.getPatientResourceProvider().getCountDelete();
}
@Override
public long getPatientCountUpdate() {
return myRestServer.getPatientResourceProvider().getCountUpdate();
}
@Override
public long getPatientCountRead() {
return myRestServer.getPatientResourceProvider().getCountRead();
}
@Override
public long getObservationCountSearch() {
return myRestServer.getObservationResourceProvider().getCountSearch();
}
@Override
public long getObservationCountDelete() {
return myRestServer.getObservationResourceProvider().getCountDelete();
}
@Override
public long getObservationCountUpdate() {
return myRestServer.getObservationResourceProvider().getCountUpdate();
}
@Override
public long getObservationCountRead() {
return myRestServer.getObservationResourceProvider().getCountRead();
}
@Override
public boolean registerInterceptor(Object theInterceptor) {
return myRestServer.getInterceptorService().registerInterceptor(theInterceptor);
}
public void registerProvider(Object theProvider) {
myRestServer.registerProvider(theProvider);
}
public void setExpectedCount(int theCount) {
myRestServer.getPlainProvider().setExpectedCount(theCount);
}
public List<HookParams> awaitExpected() throws InterruptedException {
return myRestServer.getPlainProvider().awaitExpected();
}
@Override
public HashMapResourceProvider<Observation> getObservationResourceProvider() {
return myRestServer.getObservationResourceProvider();
}
@Override
public HashMapResourceProvider<Patient> getPatientResourceProvider() {
return myRestServer.getPatientResourceProvider();
}
@Override
public HashMapResourceProvider<ConceptMap> getConceptMapResourceProvider() {
return myRestServer.getConceptMapResourceProvider();
}
public void setConceptMapResourceProvider(HashMapResourceProvider<ConceptMap> theResourceProvider) {
myRestServer.setConceptMapResourceProvider(theResourceProvider);
}
@Override
public IIdType createPatientWithId(String theId) {
Patient patient = new Patient();
patient.setId("Patient/" + theId);
patient.addIdentifier().setSystem("http://foo").setValue(theId);
return this.createPatient(patient);
}
@Override
public IIdType createPatient(IBaseResource theBaseResource) {
return myRestServer.getPatientResourceProvider().store((Patient) theBaseResource);
}
@Override
public IIdType createObservationForPatient(IIdType thePatientId) {
Observation observation = new Observation();
observation.setSubject(new Reference(thePatientId));
return this.createObservation(observation);
//TODO maybe add some data to this obs?
}
@Override
public IIdType createObservation(IBaseResource theBaseResource) {
return myRestServer.getObservationResourceProvider().store((Observation) theBaseResource);
}
public List<String> getRequestUrls() {
return myRestServer.myRequestUrls;
}
public List<String> getRequestVerbs() {
return myRestServer.myRequestVerbs;
}
public void setObservationResourceProvider(HashMapResourceProvider<Observation> theResourceProvider) {
myRestServer.setObservationResourceProvider(theResourceProvider);
}
public List<Map<String, String>> getRequestHeaders() {
return myRestServer.myRequestHeaders;
}
public IInterceptorService getInterceptorService() {
return myRestServer.getInterceptorService();
}
@Override
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy){
myRestServer.setServerAddressStrategy(theServerAddressStrategy);
}
private static class MyRestfulServer extends RestfulServer {
private boolean myFailNextPut;
private HashMapResourceProvider<Patient> myPatientResourceProvider;
private HashMapResourceProvider<Observation> myObservationResourceProvider;
private HashMapResourceProvider<Organization> myOrganizationResourceProvider;
private HashMapResourceProvider<ConceptMap> myConceptMapResourceProvider;
private RestServerDstu3Helper.MyPlainProvider myPlainProvider;
private final List<String> myRequestUrls = Collections.synchronizedList(new ArrayList<>());
private final List<String> myRequestVerbs = Collections.synchronizedList(new ArrayList<>());
private final List<Map<String, String>> myRequestHeaders= Collections.synchronizedList(new ArrayList<>());
public MyRestfulServer(FhirContext theFhirContext) {
super(theFhirContext);
}
public RestServerDstu3Helper.MyPlainProvider getPlainProvider() {
return myPlainProvider;
}
public void setFailNextPut(boolean theFailNextPut) {
myFailNextPut = theFailNextPut;
}
public void clearCounts() {
for (IResourceProvider next : getResourceProviders()) {
if (next instanceof HashMapResourceProvider) {
HashMapResourceProvider provider = (HashMapResourceProvider) next;
provider.clearCounts();
}
}
myPlainProvider.clear();
myRequestUrls.clear();
myRequestVerbs.clear();
}
@Override
protected void service(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
Request request = (Request) theReq;
Map<String, String> headers = pullOutHeaders(theReq);
myRequestHeaders.add(headers);
myRequestUrls.add(request.getOriginalURI());
myRequestVerbs.add(request.getMethod());
super.service(theReq, theResp);
}
private Map<String,String> pullOutHeaders(HttpServletRequest theReq) {
Enumeration<String> headerNames = theReq.getHeaderNames();
Map<String, String> headers = new HashMap<>();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, theReq.getHeader(headerName));
}
return headers;
}
public void clearDataAndCounts() {
for (IResourceProvider next : getResourceProviders()) {
if (next instanceof HashMapResourceProvider) {
HashMapResourceProvider provider = (HashMapResourceProvider) next;
provider.clear();
}
}
clearCounts();
}
public HashMapResourceProvider<Observation> getObservationResourceProvider() {
return myObservationResourceProvider;
}
public HashMapResourceProvider<Organization> getOrganizationResourceProvider() {
return myOrganizationResourceProvider;
}
public HashMapResourceProvider<ConceptMap> getConceptMapResourceProvider() {
return myConceptMapResourceProvider;
}
public HashMapResourceProvider<Patient> getPatientResourceProvider() {
return myPatientResourceProvider;
}
@Override
protected void initialize() throws ServletException {
super.initialize();
FhirContext fhirContext = getFhirContext();
myPatientResourceProvider = new MyHashMapResourceProvider(fhirContext, Patient.class);
registerProvider(myPatientResourceProvider);
myObservationResourceProvider = new MyHashMapResourceProvider(fhirContext, Observation.class);
registerProvider(myObservationResourceProvider);
myOrganizationResourceProvider = new MyHashMapResourceProvider(fhirContext, Organization.class);
registerProvider(myOrganizationResourceProvider);
myConceptMapResourceProvider = new MyHashMapResourceProvider(fhirContext, ConceptMap.class);
registerProvider(myConceptMapResourceProvider);
myPlainProvider = new RestServerDstu3Helper.MyPlainProvider();
registerProvider(myPlainProvider);
setPagingProvider(new FifoMemoryPagingProvider(20));
}
public void setObservationResourceProvider(HashMapResourceProvider<Observation> theResourceProvider) {
myObservationResourceProvider.getStoredResources().forEach(o -> theResourceProvider.store(o));
unregisterProvider(myObservationResourceProvider);
registerProvider(theResourceProvider);
myObservationResourceProvider = theResourceProvider;
}
public void setConceptMapResourceProvider(HashMapResourceProvider<ConceptMap> theResourceProvider) {
myConceptMapResourceProvider.getStoredResources().forEach(c -> theResourceProvider.store(c));
unregisterProvider(myConceptMapResourceProvider);
registerProvider(theResourceProvider);
myConceptMapResourceProvider = theResourceProvider;
}
public class MyHashMapResourceProvider<T extends IBaseResource> extends HashMapResourceProvider<T> {
public MyHashMapResourceProvider(FhirContext theContext, Class theType) {
super(theContext, theType);
}
@Override
public MethodOutcome update(T theResource, String theConditional, RequestDetails theRequestDetails) {
if (myFailNextPut) {
throw new PreconditionFailedException(Msg.code(2111)+"Failed update operation");
}
return super.update(theResource, theConditional, theRequestDetails);
}
}
}
}

View File

@ -0,0 +1,131 @@
package ca.uhn.fhir.test.utilities;
/*-
* #%L
* HAPI FHIR Test Utilities
* %%
* Copyright (C) 2014 - 2022 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.i18n.Msg;
import ca.uhn.fhir.tls.KeyStoreInfo;
import ca.uhn.fhir.tls.TlsAuthentication;
import ca.uhn.fhir.tls.TrustStoreInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.io.File;
import java.io.FileWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TlsAuthenticationTestHelper implements AfterEachCallback {
private static final String KEYSTORE_RESOURCE_PATH = "classpath:/tls/client-keystore.p12";
private static final String KEYSTORE_STOREPASS = "changeit";
private static final String KEYSTORE_KEYPASS = "changeit";
private static final String KEYSTORE_ALIAS = "client";
private static final String TRUSTSTORE_RESOURCE_PATH = "classpath:/tls/client-truststore.p12";
private static final String TRUSTSTORE_STOREPASS = "changeit";
private static final String TRUSTSTORE_ALIAS = "client";
private final TlsAuthentication myTlsAuthentication;
private final KeyStoreInfo myKeystoreInfo;
private final TrustStoreInfo myTrustStoreInfo;
private File myTempFile;
public TlsAuthenticationTestHelper(){
myKeystoreInfo = new KeyStoreInfo(KEYSTORE_RESOURCE_PATH, KEYSTORE_STOREPASS, KEYSTORE_KEYPASS, KEYSTORE_ALIAS);
myTrustStoreInfo = new TrustStoreInfo(TRUSTSTORE_RESOURCE_PATH, TRUSTSTORE_STOREPASS, TRUSTSTORE_ALIAS);
myTlsAuthentication = new TlsAuthentication(Optional.of(myKeystoreInfo), Optional.of(myTrustStoreInfo));
}
@Override
public void afterEach(ExtensionContext theExtensionContext) throws Exception {
if(myTempFile != null && myTempFile.exists()){
assertTrue(myTempFile.delete());
myTempFile = null;
}
}
public String[] createBaseRequestGeneratingCommandArgs(String[] theBaseArgs, String theUrlFlag, boolean theAddTls, BaseRestServerHelper theRestServerHelper){
if(isBlank(theUrlFlag)){
return theBaseArgs;
}
String[] retVal;
if(theAddTls){
int newSize = theBaseArgs.length + 4;
retVal = Arrays.copyOf(theBaseArgs, newSize);
retVal[newSize - 4] = theUrlFlag;
retVal[newSize - 3] = theRestServerHelper.getSecureBase(); // HTTPS
myTempFile = createTlsAuthenticationFile();
retVal[newSize - 2] = "--tls-auth";
retVal[newSize - 1] = myTempFile.getAbsolutePath();
}
else {
int newSize = theBaseArgs.length + 2;
retVal = Arrays.copyOf(theBaseArgs, newSize);
retVal[newSize - 2] = theUrlFlag;
retVal[newSize - 1] = theRestServerHelper.getBase(); // HTTP
}
return retVal;
}
public TlsAuthentication getTlsAuthentication(){
return myTlsAuthentication;
}
public File createTlsAuthenticationFile() {
try {
ObjectMapper mapper = new ObjectMapper();
ObjectNode keyStore = mapper.createObjectNode();
keyStore.put("filePath", KEYSTORE_RESOURCE_PATH);
keyStore.put("storePass", KEYSTORE_STOREPASS);
keyStore.put("keyPass", KEYSTORE_KEYPASS);
keyStore.put("alias", KEYSTORE_ALIAS);
ObjectNode trustStore = mapper.createObjectNode();
trustStore.put("filePath", TRUSTSTORE_RESOURCE_PATH);
trustStore.put("storePass", TRUSTSTORE_STOREPASS);
trustStore.put("alias", TRUSTSTORE_ALIAS);
ObjectNode json = mapper.createObjectNode();
json.set("keyStore", keyStore);
json.set("trustStore", trustStore);
File inputFile = File.createTempFile("smile-unit-test", ".json");
try (FileWriter inputFileWriter = new FileWriter(inputFile, StandardCharsets.UTF_8, false)) {
IOUtils.write(json.toString(), inputFileWriter);
}
return inputFile;
} catch (Exception e) {
throw new RuntimeException(Msg.code(2122)+"Failed to load test TLS authentication file", e);
}
}
}

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE27-SNAPSHOT</version> <version>6.1.0-PRE29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

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