Merge branch 'master' into spring-batch-integration

This commit is contained in:
Tadgh 2020-06-10 12:24:19 -07:00
commit a9c704c06e
88 changed files with 4486 additions and 383 deletions

View File

@ -142,21 +142,6 @@ public enum FhirVersionEnum {
throw new IllegalStateException("Unknown version: " + this); // should not happen
}
/**
* Returns the {@link FhirVersionEnum} which corresponds to a specific version of
* FHIR. Partial version strings (e.g. "3.0") are acceptable.
*
* @return Returns null if no version exists matching the given string
*/
public static FhirVersionEnum forVersionString(String theVersionString) {
for (FhirVersionEnum next : values()) {
if (next.getFhirVersionString().startsWith(theVersionString)) {
return next;
}
}
return null;
}
private interface IVersionProvider {
String provideVersion();
}
@ -241,4 +226,44 @@ public enum FhirVersionEnum {
}
/**
* Returns the {@link FhirVersionEnum} which corresponds to a specific version of
* FHIR. Partial version strings (e.g. "3.0") are acceptable. This method will
* also accept version names such as "DSTU2", "STU3", "R5", etc.
*
* @return Returns null if no version exists matching the given string
*/
public static FhirVersionEnum forVersionString(String theVersionString) {
// Trim the point release
String versionString = theVersionString;
int firstDot = versionString.indexOf('.');
if (firstDot > 0) {
int secondDot = versionString.indexOf('.', firstDot + 1);
if (secondDot > 0) {
versionString = versionString.substring(0, secondDot);
}
}
for (FhirVersionEnum next : values()) {
if (next.getFhirVersionString().startsWith(versionString)) {
return next;
}
}
switch (theVersionString) {
case "DSTU2":
return FhirVersionEnum.DSTU2;
case "DSTU3":
case "STU3":
return FhirVersionEnum.DSTU3;
case "R4":
return FhirVersionEnum.R4;
case "R5":
return FhirVersionEnum.R5;
}
return null;
}
}

View File

@ -20,6 +20,9 @@ package ca.uhn.fhir.context.support;
* #L%
*/
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class ConceptValidationOptions {
public boolean isInferSystem() {
@ -33,4 +36,10 @@ public class ConceptValidationOptions {
private boolean myInferSystem;
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("inferSystem", myInferSystem)
.toString();
}
}

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.model.api.annotation;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Supplier;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ExampleSupplier {
Class<? extends Supplier<?>>[] value();
}

View File

@ -270,6 +270,7 @@ public class Constants {
* key will be of type {@link ca.uhn.fhir.interceptor.model.RequestPartitionId}.
*/
public static final String RESOURCE_PARTITION_ID = Constants.class.getName() + "_RESOURCE_PARTITION_ID";
public static final String CT_APPLICATION_GZIP = "application/gzip";
static {
CHARSET_UTF8 = StandardCharsets.UTF_8;

View File

@ -20,8 +20,15 @@ package ca.uhn.fhir.util;
* #L%
*/
import ca.uhn.fhir.context.*;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.List;
@ -84,4 +91,22 @@ public class BinaryUtil {
reference.setReference(theSecurityContext);
}
public static void setData(FhirContext theCtx, IBaseBinary theBinary, byte[] theBytes, String theContentType) {
getOrCreateData(theCtx, theBinary).setValue(theBytes);
String elementName = "contentType";
BaseRuntimeChildDefinition entryChild = AttachmentUtil.getChild(theCtx, theBinary, elementName);
List<IBase> entries = entryChild.getAccessor().getValues(theBinary);
IPrimitiveType<String> contentTypeElement = entries
.stream()
.map(t -> (IPrimitiveType<String>) t)
.findFirst()
.orElseGet(() -> {
IPrimitiveType<String> stringType = AttachmentUtil.newPrimitive(theCtx, "code", null);
entryChild.getMutator().setValue(theBinary, stringType);
return stringType;
});
contentTypeElement.setValue(theContentType);
}
}

View File

@ -0,0 +1,51 @@
package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
public class ResourceUtil {
/**
* This method removes the narrative from the resource, or if the resource is a bundle, removes the narrative from
* all of the resources in the bundle
*
* @param theContext The fhir context
* @param theInput The resource to remove the narrative from
*/
public static void removeNarrative(FhirContext theContext, IBaseResource theInput) {
if (theInput instanceof IBaseBundle) {
for (IBaseResource next : BundleUtil.toListOfResources(theContext, (IBaseBundle) theInput)) {
removeNarrative(theContext, next);
}
}
BaseRuntimeElementCompositeDefinition<?> element = theContext.getResourceDefinition(theInput.getClass());
BaseRuntimeChildDefinition textElement = element.getChildByName("text");
if (textElement != null) {
textElement.getMutator().setValue(theInput, null);
}
}
}

View File

@ -21,10 +21,17 @@ package ca.uhn.fhir.util;
*/
import java.io.CharArrayWriter;
import java.nio.charset.StandardCharsets;
import java.text.Normalizer;
import java.util.Arrays;
public class StringUtil {
public class StringNormalizer {
public static String normalizeStringForSearchIndexing(String theString) {
if (theString == null) {
return null;
}
CharArrayWriter outBuffer = new CharArrayWriter(theString.length());
/*
@ -51,4 +58,14 @@ public class StringNormalizer {
}
public static String toUtf8String(byte[] theBytes) {
byte[] bytes = theBytes;
if (theBytes.length >= 3) {
if (theBytes[0] == -17 && theBytes[1] == -69 && theBytes[2] == -65) {
bytes = Arrays.copyOfRange(theBytes, 3, theBytes.length);
}
}
return new String(bytes, StandardCharsets.UTF_8);
}
}

View File

@ -0,0 +1,27 @@
package ca.uhn.fhir.context;
import org.junit.Test;
import static org.junit.Assert.*;
public class FhirVersionEnumTest {
@Test
public void testGetVersionPermissive() {
assertEquals(FhirVersionEnum.DSTU2, FhirVersionEnum.forVersionString("1.0.0"));
assertEquals(FhirVersionEnum.DSTU2, FhirVersionEnum.forVersionString("1.0.1"));
assertEquals(FhirVersionEnum.DSTU3, FhirVersionEnum.forVersionString("3.0.1"));
assertEquals(FhirVersionEnum.DSTU3, FhirVersionEnum.forVersionString("3.0.2"));
assertEquals(FhirVersionEnum.DSTU3, FhirVersionEnum.forVersionString("DSTU3"));
assertEquals(FhirVersionEnum.DSTU3, FhirVersionEnum.forVersionString("STU3"));
assertEquals(FhirVersionEnum.R4, FhirVersionEnum.forVersionString("4.0.0"));
assertEquals(FhirVersionEnum.R4, FhirVersionEnum.forVersionString("4.0.1"));
assertEquals(FhirVersionEnum.R4, FhirVersionEnum.forVersionString("R4"));
assertEquals(FhirVersionEnum.R5, FhirVersionEnum.forVersionString("R5"));
}
}

View File

@ -173,6 +173,7 @@ public abstract class BaseApp {
commands.add(new ExportConceptMapToCsvCommand());
commands.add(new ImportCsvToConceptMapCommand());
commands.add(new HapiFlywayMigrateDatabaseCommand());
commands.add(new CreatePackageCommand());
return commands;
}

View File

@ -0,0 +1,210 @@
package ca.uhn.fhir.cli;
/*-
* #%L
* HAPI FHIR - Command Line Client - API
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.io.Files;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.FalseFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.hl7.fhir.utilities.cache.NpmPackage;
import org.hl7.fhir.utilities.cache.PackageGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.print.attribute.standard.MediaSize;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@SuppressWarnings("UnstableApiUsage")
public class CreatePackageCommand extends BaseCommand {
private static final Logger ourLog = LoggerFactory.getLogger(CreatePackageCommand.class);
public static final String TARGET_DIRECTORY_OPT = "target-directory";
public static final String DEPENDENCY_OPT = "dependency";
public static final String INCLUDE_EXAMPLE_OPT = "include-example";
public static final String INCLUDE_PACKAGE_OPT = "include-package";
public static final String VERSION_OPT = "version";
public static final String NAME_OPT = "name";
public static final String DESCRIPTION_OPT = "description";
private File myWorkDirectory;
private String myPackageName;
private String myPackageVersion;
private NpmPackage myPackage;
private String myPackageDescription;
@Override
public String getCommandDescription() {
return "Create an NPM package using the FHIR packging format";
}
@Override
public String getCommandName() {
return "create-package";
}
@Override
public Options getOptions() {
Options options = new Options();
addFhirVersionOption(options);
addRequiredOption(options, null, NAME_OPT, "Package Name", "The name/id of the package, e.g. \"com.example.fhir.myapp\"");
addRequiredOption(options, null, VERSION_OPT, "Package Version", "The package version. FHIR packages use SemVer, e.g. \"1.0.0\"");
addOptionalOption(options, null, DESCRIPTION_OPT, "Description", "A description for this package");
addOptionalOption(options, null, INCLUDE_PACKAGE_OPT, "File Spec", "A file spec to include in the package as a package resource/artifact");
addOptionalOption(options, null, INCLUDE_EXAMPLE_OPT, "File Spec", "A file spec to include in the package as an example resource/artifact");
addOptionalOption(options, null, TARGET_DIRECTORY_OPT, "Directory", "The directory in which to place the final package");
addOptionalOption(options, null, DEPENDENCY_OPT, "name:version", "Include this dependency, in the form \"name:version\"");
return options;
}
@Override
public void cleanup() {
try {
if (myWorkDirectory != null) {
FileUtils.deleteDirectory(myWorkDirectory);
}
} catch (IOException e) {
throw new InternalErrorException("Failed to delete temporary directory \"" + myWorkDirectory.getAbsolutePath() + "\"", e);
}
}
@Override
public void run(CommandLine theCommandLine) throws ParseException, ExecutionException {
parseFhirContext(theCommandLine);
myPackageName = theCommandLine.getOptionValue(NAME_OPT);
if (isBlank(myPackageName)) {
throw new ParseException("No package name supplied (--" + NAME_OPT + ")");
}
if (!NpmPackage.isValidName(myPackageName)) {
throw new ParseException("Invalid package name: " + myPackageName);
}
myPackageVersion = theCommandLine.getOptionValue(VERSION_OPT);
if (isBlank(myPackageVersion)) {
throw new ParseException("No package version supplied (--"+VERSION_OPT+")");
}
if (!NpmPackage.isValidVersion(myPackageVersion)) {
throw new ParseException("Invalid package version: " + myPackageVersion);
}
ourLog.info("Creating FHIR package {}#{}", myPackageName, myPackageVersion);
PackageGenerator manifestGenerator = new PackageGenerator();
manifestGenerator.name(myPackageName);
manifestGenerator.version(myPackageVersion);
manifestGenerator.description(myPackageDescription);
if (isNotBlank(myPackageDescription)) {
manifestGenerator.description(myPackageDescription);
}
String[] dependencies = theCommandLine.getOptionValues(DEPENDENCY_OPT);
for (String nextDependencyString : dependencies) {
int colonIdx = nextDependencyString.indexOf(":");
if (colonIdx == -1) {
throw new ParseException("Invalid dependency spec: " + nextDependencyString);
}
String depName = nextDependencyString.substring(0, colonIdx);
String depVersion = nextDependencyString.substring(colonIdx+1);
manifestGenerator.dependency(depName, depVersion);
}
myWorkDirectory = Files.createTempDir();
myPackage = NpmPackage.empty(manifestGenerator);
ourLog.info("Using temporary directory: {}", myWorkDirectory.getAbsolutePath());
// Package
String[] packageValues = theCommandLine.getOptionValues(INCLUDE_PACKAGE_OPT);
String folder = "package";
addFiles(packageValues, folder);
// Example
packageValues = theCommandLine.getOptionValues(INCLUDE_EXAMPLE_OPT);
folder = "example";
addFiles(packageValues, folder);
String targetDirectory = theCommandLine.getOptionValue(TARGET_DIRECTORY_OPT);
if (isBlank(targetDirectory)) {
targetDirectory = ".";
}
File targetFile = new File(new File(targetDirectory), myPackageName + "-" + myPackageVersion + ".tgz");
ourLog.info("Writing NPM file: {}", targetFile.toString());
try (FileOutputStream os = new FileOutputStream(targetFile, false)) {
myPackage.save(os);
} catch (IOException e) {
throw new ExecutionException("Failed to write file " + targetFile, e);
}
}
public void addFiles(String[] thePackageValues, String theFolder) throws ParseException, ExecutionException {
if (thePackageValues != null) {
for (String nextPackageValue : thePackageValues) {
if (!nextPackageValue.contains("/")) {
throw new ParseException("Invalid file expression: " + nextPackageValue);
}
int endIndex = nextPackageValue.lastIndexOf("/");
String path = nextPackageValue.substring(0, endIndex);
String expression = nextPackageValue.substring(endIndex + 1);
IOFileFilter filter = new WildcardFileFilter(expression);
Collection<File> files = FileUtils.listFiles(new File(path), filter, FalseFileFilter.INSTANCE);
for (File next : files) {
byte[] contentBytes;
String type;
try {
String contents = IOUtils.toString(new FileInputStream(next), StandardCharsets.UTF_8);
contentBytes = contents.getBytes(StandardCharsets.UTF_8);
type = EncodingEnum.detectEncoding(contents).newParser(myFhirCtx).parseResource(contents).fhirType();
} catch (IOException | DataFormatException e) {
throw new ExecutionException("Failed to load/parse file: " + next.getName(), e);
}
ourLog.info("Adding {} file of type {}: {}", theFolder, type, next.getName());
myPackage.addFile(theFolder, next.getName(), contentBytes, type);
}
}
}
}
}

View File

@ -0,0 +1,122 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.test.BaseTest;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.utilities.cache.NpmPackage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.rauschig.jarchivelib.Archiver;
import org.rauschig.jarchivelib.ArchiverFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
public class CreatePackageCommandTest extends BaseTest {
private static final Logger ourLog = LoggerFactory.getLogger(CreatePackageCommandTest.class);
private File myWorkDirectory;
private FhirContext myContext = FhirContext.forR4();
private File myTargetDirectory;
private File myExtractDirectory;
static {
System.setProperty("test", "true");
}
@Before
public void start() {
myWorkDirectory = Files.createTempDir();
myTargetDirectory = Files.createTempDir();
myExtractDirectory = Files.createTempDir();
}
@After
public void stop() {
try {
FileUtils.deleteDirectory(myWorkDirectory);
FileUtils.deleteDirectory(myTargetDirectory);
FileUtils.deleteDirectory(myExtractDirectory);
} catch (IOException e) {
throw new InternalErrorException("Failed to delete temporary directory \"" + myWorkDirectory.getAbsolutePath() + "\"", e);
}
}
@Test
public void testCreatePackage() throws IOException {
StructureDefinition sd = new StructureDefinition();
sd.setUrl("http://foo/1");
writeFile(sd, "foo1.json");
ValueSet vs = new ValueSet();
vs.setUrl("http://foo/2");
writeFile(vs, "foo2.json");
App.main(new String[]{
"create-package",
"--fhir-version", "R4",
"--name", "com.example.ig",
"--version", "1.0.1",
"--include-package", myWorkDirectory.getAbsolutePath() + "/*.json",
"--target-directory", myTargetDirectory.getAbsolutePath(),
"--dependency", "hl7.fhir.core:4.0.1",
"--dependency", "foo.bar:1.2.3"
});
Archiver archiver = ArchiverFactory.createArchiver("tar", "gz");
File igArchive = new File(myTargetDirectory, "com.example.ig-1.0.1.tgz");
archiver.extract(igArchive, myExtractDirectory);
List<String> allFiles = FileUtils.listFiles(myExtractDirectory, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)
.stream()
.map(t -> t.getPath())
.sorted()
.collect(Collectors.toList());
ourLog.info("Archive contains files:\n * {}", allFiles.stream().collect(Collectors.joining("\n * ")));
// Verify package.json
String packageJsonContents = IOUtils.toString(new FileInputStream(new File(myExtractDirectory, "package/package.json")), Charsets.UTF_8);
ourLog.info("Package.json:\n{}", packageJsonContents);
String expectedPackageJson = "{\n" +
" \"name\": \"com.example.ig\",\n" +
" \"version\": \"1.0.1\",\n" +
" \"dependencies\": {\n" +
" \"hl7.fhir.core\": \"4.0.1\",\n" +
" \"foo.bar\": \"1.2.3\"\n" +
" }\n" +
"}";
assertEquals(expectedPackageJson, packageJsonContents);
// Try parsing the module again to make sure we can
NpmPackage loadedPackage = NpmPackage.fromPackage(new FileInputStream(igArchive));
assertEquals("com.example.ig", loadedPackage.name());
}
public void writeFile(IBaseResource theResource, String theFileName) throws IOException {
try (FileWriter w = new FileWriter(new File(myWorkDirectory, theFileName), false)) {
myContext.newJsonParser().encodeResourceToWriter(theResource, w);
}
}
}

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 1895
title: "HAPI FHIR 5.0.0 introduced a regressin in JPA validator performance, where a number of unnecessary database lookups
were introduced. This has been corrected."

View File

@ -0,0 +1,5 @@
---
type: add
issue: 1911
title: "Support for FHIR NPM Packages has been added to the JPA server. This new functionality allows packages to
be installed to special tables within the FHIR JPA schema, and conformance resources used for validation."

View File

@ -163,7 +163,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
/**
* Read a resource by its internal PID
*/
IBaseResource readByPid(ResourcePersistentId thePid);
T readByPid(ResourcePersistentId thePid);
/**
* @param theId

View File

@ -188,6 +188,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.helger</groupId>

View File

@ -24,7 +24,11 @@ import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
import ca.uhn.fhir.jpa.packages.IgInstallerSvc;
import ca.uhn.fhir.jpa.packages.IHapiPackageCacheManager;
import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc;
import ca.uhn.fhir.jpa.packages.JpaPackageCache;
import ca.uhn.fhir.jpa.packages.PackageInstallerSvcImpl;
import ca.uhn.fhir.jpa.packages.NpmJpaValidationSupport;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl;
@ -56,6 +60,7 @@ import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.utilities.cache.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -117,15 +122,15 @@ import java.util.Date;
public abstract class BaseConfig {
public static final String JPA_VALIDATION_SUPPORT_CHAIN = "myJpaValidationSupportChain";
public static final String JPA_VALIDATION_SUPPORT = "myJpaValidationSupport";
public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor";
public static final String GRAPHQL_PROVIDER_NAME = "myGraphQLProvider";
public static final String PERSISTED_JPA_BUNDLE_PROVIDER = "PersistedJpaBundleProvider";
public static final String PERSISTED_JPA_BUNDLE_PROVIDER_BY_SEARCH = "PersistedJpaBundleProvider_BySearch";
public static final String PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER = "PersistedJpaSearchFirstPageBundleProvider";
private static final String HAPI_DEFAULT_SCHEDULER_GROUP = "HAPI";
public static final String SEARCH_BUILDER = "SearchBuilder";
public static final String HISTORY_BUILDER = "HistoryBuilder";
private static final String HAPI_DEFAULT_SCHEDULER_GROUP = "HAPI";
@Autowired
protected Environment myEnv;
@Autowired
@ -202,6 +207,20 @@ public abstract class BaseConfig {
return new DaoResourceLinkResolver();
}
@Bean
public IHapiPackageCacheManager packageCacheManager() {
JpaPackageCache retVal = new JpaPackageCache();
retVal.getPackageServers().clear();
retVal.getPackageServers().add(FilesystemPackageCacheManager.PRIMARY_SERVER);
retVal.getPackageServers().add(FilesystemPackageCacheManager.SECONDARY_SERVER);
return retVal;
}
@Bean
public NpmJpaValidationSupport npmJpaValidationSupport() {
return new NpmJpaValidationSupport();
}
@Bean
public ISearchCacheSvc searchCacheSvc() {
return new DatabaseSearchCacheSvcImpl();
@ -266,7 +285,9 @@ public abstract class BaseConfig {
}
@Bean
public IgInstallerSvc igInstallerSvc() { return new IgInstallerSvc(); }
public IPackageInstallerSvc npmInstallerSvc() {
return new PackageInstallerSvcImpl();
}
@Bean
public IConsentContextServices consentContextServices() {

View File

@ -65,16 +65,16 @@ public abstract class BaseConfigDstu3Plus extends BaseConfig {
public abstract ITermVersionAdapterSvc terminologyVersionAdapterSvc();
@Bean(name = "myDefaultProfileValidationSupport")
public IValidationSupport defaultProfileValidationSupport() {
public DefaultProfileValidationSupport defaultProfileValidationSupport() {
return new DefaultProfileValidationSupport(fhirContext());
}
@Bean(name = JPA_VALIDATION_SUPPORT_CHAIN)
public ValidationSupportChain jpaValidationSupportChain() {
public JpaValidationSupportChain jpaValidationSupportChain() {
return new JpaValidationSupportChain(fhirContext());
}
@Bean(name = "myJpaValidationSupport")
@Bean(name = JPA_VALIDATION_SUPPORT)
public IValidationSupport jpaValidationSupport() {
return new JpaPersistedResourceValidationSupport(fhirContext());
}

View File

@ -86,28 +86,30 @@ public class BaseDstu2Config extends BaseConfig {
@Bean(name = "myInstanceValidator")
@Lazy
public IInstanceValidatorModule instanceValidator() {
ValidationSupportChain validationSupportChain = validationSupportChain();
CachingValidationSupport cachingValidationSupport = new CachingValidationSupport(new HapiToHl7OrgDstu2ValidatingSupportWrapper(validationSupportChain));
public IInstanceValidatorModule instanceValidator(ValidationSupportChain theValidationSupportChain) {
CachingValidationSupport cachingValidationSupport = new CachingValidationSupport(new HapiToHl7OrgDstu2ValidatingSupportWrapper(theValidationSupportChain));
FhirInstanceValidator retVal = new FhirInstanceValidator(cachingValidationSupport);
retVal.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
return retVal;
}
@Bean(name = "myDefaultProfileValidationSupport")
public DefaultProfileValidationSupport defaultProfileValidationSupport() {
return new DefaultProfileValidationSupport(fhirContext());
}
@Bean(name = JPA_VALIDATION_SUPPORT_CHAIN)
public ValidationSupportChain validationSupportChain() {
DefaultProfileValidationSupport defaultProfileValidationSupport = new DefaultProfileValidationSupport(fhirContext());
public ValidationSupportChain validationSupportChain(DefaultProfileValidationSupport theDefaultProfileValidationSupport) {
InMemoryTerminologyServerValidationSupport inMemoryTerminologyServer = new InMemoryTerminologyServerValidationSupport(fhirContextDstu2());
IValidationSupport jpaValidationSupport = jpaValidationSupportDstu2();
CommonCodeSystemsTerminologyService commonCodeSystemsTermSvc = new CommonCodeSystemsTerminologyService(fhirContext());
return new ValidationSupportChain(defaultProfileValidationSupport, jpaValidationSupport, inMemoryTerminologyServer, commonCodeSystemsTermSvc);
return new ValidationSupportChain(theDefaultProfileValidationSupport, jpaValidationSupport, inMemoryTerminologyServer, commonCodeSystemsTermSvc);
}
@Primary
@Bean
@Bean(name = JPA_VALIDATION_SUPPORT)
public IValidationSupport jpaValidationSupportDstu2() {
JpaPersistedResourceValidationSupport retVal = new JpaPersistedResourceValidationSupport(fhirContextDstu2());
return retVal;
return new JpaPersistedResourceValidationSupport(fhirContextDstu2());
}
@Bean(name = "myResourceCountsCache")

View File

@ -932,7 +932,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
public IBaseResource readByPid(ResourcePersistentId thePid) {
public T readByPid(ResourcePersistentId thePid) {
StopWatch w = new StopWatch();
Optional<ResourceTable> entity = myResourceTableDao.findById(thePid.getIdAsLong());

View File

@ -99,6 +99,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static ca.uhn.fhir.util.StringUtil.toUtf8String;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -768,14 +769,14 @@ public abstract class BaseTransactionProcessor {
String matchUrl = toMatchUrl(nextReqEntry);
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
String patchBody = null;
String contentType = null;
String contentType;
IBaseParameters patchBodyParameters = null;
PatchTypeEnum patchType = null;
if (res instanceof IBaseBinary) {
IBaseBinary binary = (IBaseBinary) res;
if (binary.getContent() != null && binary.getContent().length > 0) {
patchBody = new String(binary.getContent(), Charsets.UTF_8);
patchBody = toUtf8String(binary.getContent());
}
contentType = binary.getContentType();
patchType = PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(myContext, contentType);

View File

@ -28,6 +28,8 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.UriParam;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -43,6 +45,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.transaction.Transactional;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -57,6 +60,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
private static final Logger ourLog = LoggerFactory.getLogger(JpaPersistedResourceValidationSupport.class);
private final FhirContext myFhirContext;
private final IBaseResource myNoMatch;
@Autowired
private DaoRegistry myDaoRegistry;
@ -65,6 +69,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
private Class<? extends IBaseResource> myValueSetType;
private Class<? extends IBaseResource> myQuestionnaireType;
private Class<? extends IBaseResource> myImplementationGuideType;
private Cache<String, IBaseResource> myLoadCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
/**
* Constructor
@ -73,6 +78,8 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
super();
Validate.notNull(theFhirContext);
myFhirContext = theFhirContext;
myNoMatch = myFhirContext.getResourceDefinition("Basic").newInstance();
}
@ -99,77 +106,86 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
return null;
}
IdType id = new IdType(theUri);
boolean localReference = false;
if (id.hasBaseUrl() == false && id.hasIdPart() == true) {
localReference = true;
}
String key = theClass.getSimpleName() + " " + theUri;
IBaseResource fetched = myLoadCache.get(key, t -> {
IdType id = new IdType(theUri);
boolean localReference = false;
if (id.hasBaseUrl() == false && id.hasIdPart() == true) {
localReference = true;
}
String resourceName = myFhirContext.getResourceType(theClass);
IBundleProvider search;
if ("ValueSet".equals(resourceName)) {
if (localReference) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(IAnyResource.SP_RES_ID, new StringParam(theUri));
search = myDaoRegistry.getResourceDao("ValueSet").search(params);
if (search.size() == 0) {
params = new SearchParameterMap();
String resourceName = myFhirContext.getResourceType(theClass);
IBundleProvider search;
if ("ValueSet".equals(resourceName)) {
if (localReference) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(IAnyResource.SP_RES_ID, new StringParam(theUri));
search = myDaoRegistry.getResourceDao("ValueSet").search(params);
if (search.size() == 0) {
params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ValueSet.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao("ValueSet").search(params);
}
} else {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ValueSet.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao("ValueSet").search(params);
}
} else {
} else if ("StructureDefinition".equals(resourceName)) {
// Don't allow the core FHIR definitions to be overwritten
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length());
if (myFhirContext.getElementDefinition(typeName) != null) {
return myNoMatch;
}
}
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ValueSet.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao("ValueSet").search(params);
}
} else if ("StructureDefinition".equals(resourceName)) {
// Don't allow the core FHIR definitions to be overwritten
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length());
if (myFhirContext.getElementDefinition(typeName) != null) {
return null;
params.add(StructureDefinition.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao("StructureDefinition").search(params);
} else if ("Questionnaire".equals(resourceName)) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
if (localReference || myFhirContext.getVersion().getVersion().isEquivalentTo(FhirVersionEnum.DSTU2)) {
params.add(IAnyResource.SP_RES_ID, new StringParam(id.getIdPart()));
} else {
params.add(Questionnaire.SP_URL, new UriParam(id.getValue()));
}
}
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(StructureDefinition.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao("StructureDefinition").search(params);
} else if ("Questionnaire".equals(resourceName)) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
if (localReference || myFhirContext.getVersion().getVersion().isEquivalentTo(FhirVersionEnum.DSTU2)) {
params.add(IAnyResource.SP_RES_ID, new StringParam(id.getIdPart()));
search = myDaoRegistry.getResourceDao("Questionnaire").search(params);
} else if ("CodeSystem".equals(resourceName)) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(CodeSystem.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
} else if ("ImplementationGuide".equals(resourceName)) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ImplementationGuide.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao("ImplementationGuide").search(params);
} else {
params.add(Questionnaire.SP_URL, new UriParam(id.getValue()));
throw new IllegalArgumentException("Can't fetch resource type: " + resourceName);
}
search = myDaoRegistry.getResourceDao("Questionnaire").search(params);
} else if ("CodeSystem".equals(resourceName)) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(CodeSystem.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
} else if ("ImplementationGuide".equals(resourceName)) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ImplementationGuide.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao("ImplementationGuide").search(params);
} else {
throw new IllegalArgumentException("Can't fetch resource type: " + resourceName);
}
Integer size = search.size();
if (size == null || size == 0) {
Integer size = search.size();
if (size == null || size == 0) {
return myNoMatch;
}
if (size > 1) {
ourLog.warn("Found multiple {} instances with URL search value of: {}", resourceName, theUri);
}
return search.getResources(0, 1).get(0);
});
if (fetched == myNoMatch) {
return null;
}
if (size > 1) {
ourLog.warn("Found multiple {} instances with URL search value of: {}", resourceName, theUri);
}
return (T) search.getResources(0, 1).get(0);
return (T) fetched;
}
@Override
@ -192,4 +208,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
}
public void clearCaches() {
myLoadCache.invalidateAll();
}
}

View File

@ -1377,7 +1377,7 @@ public class SearchBuilder implements ISearchBuilder {
return ResourcePersistentId.fromLongList(query.getResultList());
}
static Predicate[] toPredicateArray(List<Predicate> thePredicates) {
public static Predicate[] toPredicateArray(List<Predicate> thePredicates) {
return thePredicates.toArray(new Predicate[0]);
}
}

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.model.entity.NpmPackageEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface INpmPackageDao extends JpaRepository<NpmPackageEntity, Long> {
@Query("SELECT p FROM NpmPackageEntity p WHERE p.myPackageId = :id")
Optional<NpmPackageEntity> findByPackageId(@Param("id") String thePackageId);
}

View File

@ -0,0 +1,45 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface INpmPackageVersionDao extends JpaRepository<NpmPackageVersionEntity, Long> {
@Query("SELECT p FROM NpmPackageVersionEntity p WHERE p.myPackageId = :id")
Collection<NpmPackageVersionEntity> findByPackageId(@Param("id") String thePackageId);
@Query("SELECT p FROM NpmPackageVersionEntity p WHERE p.myPackageId = :id AND p.myVersionId = :version")
Optional<NpmPackageVersionEntity> findByPackageIdAndVersion(@Param("id") String thePackageId, @Param("version") String thePackageVersion);
/**
* Uses a "like" expression on the version ID
*/
@Query("SELECT p.myVersionId FROM NpmPackageVersionEntity p WHERE p.myPackageId = :id AND p.myVersionId like :version")
List<String> findVersionIdsByPackageIdAndLikeVersion(@Param("id") String theId, @Param("version") String thePartialVersionString);
}

View File

@ -0,0 +1,38 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionResourceEntity;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface INpmPackageVersionResourceDao extends JpaRepository<NpmPackageVersionResourceEntity, Long> {
@Query("SELECT e FROM NpmPackageVersionResourceEntity e WHERE e.myCanonicalUrl = :url AND e.myFhirVersion = :fhirVersion AND e.myPackageVersion.myCurrentVersion = true")
Slice<NpmPackageVersionResourceEntity> findCurrentVersionByCanonicalUrl(Pageable thePage, @Param("fhirVersion") FhirVersionEnum theFhirVersion, @Param("url") String theCanonicalUrl);
@Query("SELECT e FROM NpmPackageVersionResourceEntity e WHERE e.myCanonicalUrl = :url AND e.myCanonicalVersion = :version AND e.myFhirVersion = :fhirVersion AND e.myPackageVersion.myCurrentVersion = true")
Slice<NpmPackageVersionResourceEntity> findCurrentVersionByCanonicalUrlAndVersion(Pageable theOf, @Param("fhirVersion") FhirVersionEnum theFhirVersion, @Param("url") String theCanonicalUrl, @Param("version") String theCanonicalVersion);
}

View File

@ -41,6 +41,9 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.NpmPackageEntity;
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity;
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionResourceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTag;
@ -114,6 +117,9 @@ public class ExpungeEverythingService {
counter.addAndGet(doExpungeEverythingQuery("UPDATE " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null"));
return null;
});
counter.addAndGet(expungeEverythingByType(NpmPackageVersionResourceEntity.class));
counter.addAndGet(expungeEverythingByType(NpmPackageVersionEntity.class));
counter.addAndGet(expungeEverythingByType(NpmPackageEntity.class));
counter.addAndGet(expungeEverythingByType(SearchParamPresent.class));
counter.addAndGet(expungeEverythingByType(ForcedId.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamDate.class));

View File

@ -30,7 +30,7 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.StringNormalizer;
import ca.uhn.fhir.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@ -131,7 +131,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
}
if (myDontUseHashesForSearch) {
String likeExpression = StringNormalizer.normalizeStringForSearchIndexing(rawSearchTerm);
String likeExpression = StringUtil.normalizeStringForSearchIndexing(rawSearchTerm);
if (myDaoConfig.isAllowContainsSearches()) {
if (theParameter instanceof StringParam) {
if (((StringParam) theParameter).isContains()) {
@ -161,7 +161,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash);
} else {
// Normalized Match
String normalizedString = StringNormalizer.normalizeStringForSearchIndexing(rawSearchTerm);
String normalizedString = StringUtil.normalizeStringForSearchIndexing(rawSearchTerm);
String likeExpression;
if ((theParameter instanceof StringParam) &&
(((((StringParam) theParameter).isContains()) &&

View File

@ -27,7 +27,19 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.*;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
@ -127,6 +139,11 @@ public class TermCodeSystemVersion implements Serializable {
return this;
}
public TermCodeSystemVersion setId(Long theId) {
myId = theId;
return this;
}
@Override
public boolean equals(Object theO) {
if (this == theO) {

View File

@ -0,0 +1,98 @@
package ca.uhn.fhir.jpa.packages;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.utilities.cache.IPackageCacheManager;
import org.hl7.fhir.utilities.cache.NpmPackage;
import java.io.IOException;
import java.util.Date;
public interface IHapiPackageCacheManager extends IPackageCacheManager {
NpmPackage installPackage(PackageInstallationSpec theInstallationSpec) throws IOException;
IBaseResource loadPackageAssetByUrl(FhirVersionEnum theFhirVersion, String theCanonicalUrl);
NpmPackageMetadataJson loadPackageMetadata(String thePackageId) throws ResourceNotFoundException;
PackageContents loadPackageContents(String thePackageId, String theVersion);
NpmPackageSearchResultJson search(PackageSearchSpec thePackageSearchSpec);
PackageDeleteOutcomeJson uninstallPackage(String thePackageId, String theVersion);
class PackageContents {
private byte[] myBytes;
private String myPackageId;
private String myVersion;
private Date myLastModified;
/**
* Constructor
*/
public PackageContents() {
super();
}
public byte[] getBytes() {
return myBytes;
}
public PackageContents setBytes(byte[] theBytes) {
myBytes = theBytes;
return this;
}
public String getPackageId() {
return myPackageId;
}
public PackageContents setPackageId(String thePackageId) {
myPackageId = thePackageId;
return this;
}
public String getVersion() {
return myVersion;
}
public PackageContents setVersion(String theVersion) {
myVersion = theVersion;
return this;
}
public Date getLastModified() {
return myLastModified;
}
public PackageContents setLastModified(Date theLastModified) {
myLastModified = theLastModified;
return this;
}
}
}

View File

@ -0,0 +1,27 @@
package ca.uhn.fhir.jpa.packages;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface IPackageInstallerSvc {
PackageInstallOutcomeJson install(PackageInstallationSpec theSpec);
}

View File

@ -0,0 +1,652 @@
package ca.uhn.fhir.jpa.packages;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.dao.data.INpmPackageDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionResourceDao;
import ca.uhn.fhir.jpa.model.entity.NpmPackageEntity;
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity;
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionResourceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.BinaryUtil;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.ResourceUtil;
import ca.uhn.fhir.util.StringUtil;
import org.apache.commons.collections4.comparators.ReverseComparator;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.utilities.cache.BasePackageCacheManager;
import org.hl7.fhir.utilities.cache.NpmPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.SearchBuilder.toPredicateArray;
import static ca.uhn.fhir.util.StringUtil.toUtf8String;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class JpaPackageCache extends BasePackageCacheManager implements IHapiPackageCacheManager {
public static final String UTF8_BOM = "\uFEFF";
private static final Logger ourLog = LoggerFactory.getLogger(JpaPackageCache.class);
private final Map<FhirVersionEnum, FhirContext> myVersionToContext = Collections.synchronizedMap(new HashMap<>());
@PersistenceContext
protected EntityManager myEntityManager;
@Autowired
private INpmPackageDao myPackageDao;
@Autowired
private INpmPackageVersionDao myPackageVersionDao;
@Autowired
private INpmPackageVersionResourceDao myPackageVersionResourceDao;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
private FhirContext myCtx;
@Autowired
private PlatformTransactionManager myTxManager;
@Override
public NpmPackage loadPackageFromCacheOnly(String theId, @Nullable String theVersion) {
Optional<NpmPackageVersionEntity> packageVersion = loadPackageVersionEntity(theId, theVersion);
if (!packageVersion.isPresent() && theVersion.endsWith(".x")) {
String lookupVersion = theVersion;
do {
lookupVersion = lookupVersion.substring(0, lookupVersion.length() - 2);
} while (lookupVersion.endsWith(".x"));
List<String> candidateVersionIds = myPackageVersionDao.findVersionIdsByPackageIdAndLikeVersion(theId, lookupVersion + ".%");
if (candidateVersionIds.size() > 0) {
candidateVersionIds.sort(PackageVersionComparator.INSTANCE);
packageVersion = loadPackageVersionEntity(theId, candidateVersionIds.get(candidateVersionIds.size() - 1));
}
}
return packageVersion.map(t -> loadPackage(t)).orElse(null);
}
private Optional<NpmPackageVersionEntity> loadPackageVersionEntity(String theId, @Nullable String theVersion) {
Validate.notBlank(theId, "theId must be populated");
Optional<NpmPackageVersionEntity> packageVersion = Optional.empty();
if (isNotBlank(theVersion) && !"latest".equals(theVersion)) {
packageVersion = myPackageVersionDao.findByPackageIdAndVersion(theId, theVersion);
} else {
Optional<NpmPackageEntity> pkg = myPackageDao.findByPackageId(theId);
if (pkg.isPresent()) {
packageVersion = myPackageVersionDao.findByPackageIdAndVersion(theId, pkg.get().getCurrentVersionId());
}
}
return packageVersion;
}
private NpmPackage loadPackage(NpmPackageVersionEntity thePackageVersion) {
PackageContents content = loadPackageContents(thePackageVersion);
ByteArrayInputStream inputStream = new ByteArrayInputStream(content.getBytes());
try {
return NpmPackage.fromPackage(inputStream);
} catch (IOException e) {
throw new InternalErrorException(e);
}
}
private IHapiPackageCacheManager.PackageContents loadPackageContents(NpmPackageVersionEntity thePackageVersion) {
IFhirResourceDao<? extends IBaseBinary> binaryDao = getBinaryDao();
IBaseBinary binary = binaryDao.readByPid(new ResourcePersistentId(thePackageVersion.getPackageBinary().getId()));
PackageContents retVal = new PackageContents()
.setBytes(binary.getContent())
.setPackageId(thePackageVersion.getPackageId())
.setVersion(thePackageVersion.getVersionId())
.setLastModified(thePackageVersion.getUpdatedTime());
return retVal;
}
@SuppressWarnings("unchecked")
private IFhirResourceDao<IBaseBinary> getBinaryDao() {
return myDaoRegistry.getResourceDao("Binary");
}
@Override
public NpmPackage addPackageToCache(String thePackageId, String thePackageVersionId, InputStream thePackageTgzInputStream, String theSourceDesc) throws IOException {
Validate.notBlank(thePackageId, "thePackageId must not be null");
Validate.notBlank(thePackageVersionId, "thePackageVersionId must not be null");
Validate.notNull(thePackageTgzInputStream, "thePackageTgzInputStream must not be null");
byte[] bytes = IOUtils.toByteArray(thePackageTgzInputStream);
NpmPackage npmPackage = NpmPackage.fromPackage(new ByteArrayInputStream(bytes));
if (!npmPackage.id().equals(thePackageId)) {
throw new InvalidRequestException("Package ID " + npmPackage.id() + " doesn't match expected: " + thePackageId);
}
if (!PackageVersionComparator.isEquivalent(thePackageVersionId, npmPackage.version())) {
throw new InvalidRequestException("Package ID " + npmPackage.version() + " doesn't match expected: " + thePackageVersionId);
}
String packageVersionId = npmPackage.version();
FhirVersionEnum fhirVersion = FhirVersionEnum.forVersionString(npmPackage.fhirVersion());
if (fhirVersion == null) {
throw new InvalidRequestException("Unknown FHIR version: " + npmPackage.fhirVersion());
}
FhirContext packageContext = getFhirContext(fhirVersion);
IBaseBinary binary = createPackageBinary(bytes);
return newTxTemplate().execute(tx -> {
ResourceTable persistedPackage = (ResourceTable) getBinaryDao().create(binary).getEntity();
NpmPackageEntity pkg = myPackageDao.findByPackageId(thePackageId).orElseGet(() -> createPackage(npmPackage));
NpmPackageVersionEntity packageVersion = myPackageVersionDao.findByPackageIdAndVersion(thePackageId, packageVersionId).orElse(null);
if (packageVersion != null) {
NpmPackage existingPackage = loadPackageFromCacheOnly(packageVersion.getPackageId(), packageVersion.getVersionId());
String msg = "Package version already exists in local storage, no action taken: " + thePackageId + "#" + packageVersionId;
getProcessingMessages(existingPackage).add(msg);
ourLog.info(msg);
return existingPackage;
}
boolean currentVersion = updateCurrentVersionFlagForAllPackagesBasedOnNewIncomingVersion(thePackageId, packageVersionId);
if (currentVersion) {
getProcessingMessages(npmPackage).add("Marking package " + thePackageId + "#" + thePackageVersionId + " as current version");
pkg.setCurrentVersionId(packageVersionId);
pkg.setDescription(npmPackage.description());
myPackageDao.save(pkg);
} else {
getProcessingMessages(npmPackage).add("Package " + thePackageId + "#" + thePackageVersionId + " is not the newest version");
}
packageVersion = new NpmPackageVersionEntity();
packageVersion.setPackageId(thePackageId);
packageVersion.setVersionId(packageVersionId);
packageVersion.setPackage(pkg);
packageVersion.setPackageBinary(persistedPackage);
packageVersion.setSavedTime(new Date());
packageVersion.setDescription(npmPackage.description());
packageVersion.setFhirVersionId(npmPackage.fhirVersion());
packageVersion.setFhirVersion(fhirVersion);
packageVersion.setCurrentVersion(currentVersion);
packageVersion.setPackageSizeBytes(bytes.length);
packageVersion = myPackageVersionDao.save(packageVersion);
String dirName = "package";
NpmPackage.NpmPackageFolder packageFolder = npmPackage.getFolders().get(dirName);
for (Map.Entry<String, List<String>> nextTypeToFiles : packageFolder.getTypes().entrySet()) {
String nextType = nextTypeToFiles.getKey();
for (String nextFile : nextTypeToFiles.getValue()) {
byte[] contents;
String contentsString;
try {
contents = packageFolder.fetchFile(nextFile);
contentsString = toUtf8String(contents);
} catch (IOException e) {
throw new InternalErrorException(e);
}
IBaseResource resource;
if (nextFile.toLowerCase().endsWith(".xml")) {
resource = packageContext.newXmlParser().parseResource(contentsString);
} else if (nextFile.toLowerCase().endsWith(".json")) {
resource = packageContext.newJsonParser().parseResource(contentsString);
} else {
getProcessingMessages(npmPackage).add("Not indexing file: " + nextFile);
continue;
}
/*
* Re-encode the resource as JSON with the narrative removed in order to reduce the footprint.
* This is useful since we'll be loading these resources back and hopefully keeping lots of
* them in memory in order to speed up validation activities.
*/
String contentType = Constants.CT_FHIR_JSON_NEW;
ResourceUtil.removeNarrative(packageContext, resource);
byte[] minimizedContents = packageContext.newJsonParser().encodeResourceToString(resource).getBytes(StandardCharsets.UTF_8);
IBaseBinary resourceBinary = createPackageResourceBinary(nextFile, minimizedContents, contentType);
ResourceTable persistedResource = (ResourceTable) getBinaryDao().create(resourceBinary).getEntity();
NpmPackageVersionResourceEntity resourceEntity = new NpmPackageVersionResourceEntity();
resourceEntity.setPackageVersion(packageVersion);
resourceEntity.setResourceBinary(persistedResource);
resourceEntity.setDirectory(dirName);
resourceEntity.setFhirVersionId(npmPackage.fhirVersion());
resourceEntity.setFhirVersion(fhirVersion);
resourceEntity.setFilename(nextFile);
resourceEntity.setResourceType(nextType);
resourceEntity.setResSizeBytes(contents.length);
BaseRuntimeChildDefinition urlChild = packageContext.getResourceDefinition(nextType).getChildByName("url");
BaseRuntimeChildDefinition versionChild = packageContext.getResourceDefinition(nextType).getChildByName("version");
String url = null;
String version = null;
if (urlChild != null) {
url = urlChild.getAccessor().getFirstValueOrNull(resource).map(t -> ((IPrimitiveType<?>) t).getValueAsString()).orElse(null);
resourceEntity.setCanonicalUrl(url);
version = versionChild.getAccessor().getFirstValueOrNull(resource).map(t -> ((IPrimitiveType<?>) t).getValueAsString()).orElse(null);
resourceEntity.setCanonicalVersion(version);
}
myPackageVersionResourceDao.save(resourceEntity);
String msg = "Indexing Resource[" + dirName + '/' + nextFile + "] with URL: " + defaultString(url) + "|" + defaultString(version);
getProcessingMessages(npmPackage).add(msg);
ourLog.info("Package[{}#{}] " + msg, thePackageId, packageVersionId);
}
}
getProcessingMessages(npmPackage).add("Successfully added package " + npmPackage.id() + "#" + npmPackage.version() + " to registry");
return npmPackage;
});
}
private boolean updateCurrentVersionFlagForAllPackagesBasedOnNewIncomingVersion(String thePackageId, String thePackageVersion) {
Collection<NpmPackageVersionEntity> existingVersions = myPackageVersionDao.findByPackageId(thePackageId);
boolean retVal = true;
for (NpmPackageVersionEntity next : existingVersions) {
int cmp = PackageVersionComparator.INSTANCE.compare(next.getVersionId(), thePackageVersion);
assert cmp != 0;
if (cmp < 0) {
if (next.isCurrentVersion()) {
next.setCurrentVersion(false);
myPackageVersionDao.save(next);
}
} else {
retVal = false;
}
}
return retVal;
}
@Nonnull
public FhirContext getFhirContext(FhirVersionEnum theFhirVersion) {
return myVersionToContext.computeIfAbsent(theFhirVersion, v -> new FhirContext(v));
}
private IBaseBinary createPackageBinary(byte[] theBytes) {
IBaseBinary binary = BinaryUtil.newBinary(myCtx);
BinaryUtil.setData(myCtx, binary, theBytes, Constants.CT_APPLICATION_GZIP);
return binary;
}
private IBaseBinary createPackageResourceBinary(String theFileName, byte[] theBytes, String theContentType) {
IBaseBinary binary = BinaryUtil.newBinary(myCtx);
BinaryUtil.setData(myCtx, binary, theBytes, theContentType);
return binary;
}
private NpmPackageEntity createPackage(NpmPackage theNpmPackage) {
NpmPackageEntity entity = new NpmPackageEntity();
entity.setPackageId(theNpmPackage.id());
entity.setCurrentVersionId(theNpmPackage.version());
return myPackageDao.save(entity);
}
@Override
public NpmPackage loadPackage(String thePackageId, String thePackageVersion) throws FHIRException, IOException {
NpmPackage cachedPackage = loadPackageFromCacheOnly(thePackageId, thePackageVersion);
if (cachedPackage != null) {
return cachedPackage;
}
InputStreamWithSrc pkg = super.loadFromPackageServer(thePackageId, thePackageVersion);
if (pkg == null) {
throw new ResourceNotFoundException("Unable to locate package " + thePackageId + "#" + thePackageVersion);
}
try {
NpmPackage retVal = addPackageToCache(thePackageId, thePackageVersion == null ? pkg.version : thePackageVersion, pkg.stream, pkg.url);
getProcessingMessages(retVal).add(0, "Package fetched from server at: " + pkg.url);
return retVal;
} finally {
pkg.stream.close();
}
}
private TransactionTemplate newTxTemplate() {
return new TransactionTemplate(myTxManager);
}
@Override
public NpmPackage installPackage(PackageInstallationSpec theInstallationSpec) throws IOException {
Validate.notBlank(theInstallationSpec.getName(), "thePackageId must not be blank");
Validate.notBlank(theInstallationSpec.getVersion(), "thePackageVersion must not be blank");
if (isNotBlank(theInstallationSpec.getPackageUrl())) {
byte[] contents = loadPackageUrlContents(theInstallationSpec.getPackageUrl());
theInstallationSpec.setPackageContents(contents);
}
if (theInstallationSpec.getPackageContents() != null) {
return addPackageToCache(theInstallationSpec.getName(), theInstallationSpec.getVersion(), new ByteArrayInputStream(theInstallationSpec.getPackageContents()), "Manually added");
}
return loadPackage(theInstallationSpec.getName(), theInstallationSpec.getVersion());
}
protected byte[] loadPackageUrlContents(String thePackageUrl) {
if (thePackageUrl.startsWith("classpath:")) {
return ClasspathUtil.loadResourceAsByteArray(thePackageUrl.substring("classpath:".length()));
} else {
HttpClientConnectionManager connManager = new BasicHttpClientConnectionManager();
try (CloseableHttpResponse request = HttpClientBuilder
.create()
.setConnectionManager(connManager)
.build()
.execute(new HttpGet(thePackageUrl))) {
if (request.getStatusLine().getStatusCode() != 200) {
throw new IOException("Received HTTP " + request.getStatusLine().getStatusCode());
}
return IOUtils.toByteArray(request.getEntity().getContent());
} catch (IOException e) {
throw new InternalErrorException("Error loading \"" + thePackageUrl + "\": " + e.getMessage());
}
}
}
@Override
@Transactional
public IBaseResource loadPackageAssetByUrl(FhirVersionEnum theFhirVersion, String theCanonicalUrl) {
String canonicalUrl = theCanonicalUrl;
int versionSeparator = canonicalUrl.lastIndexOf('|');
Slice<NpmPackageVersionResourceEntity> slice;
if (versionSeparator != -1) {
String canonicalVersion = canonicalUrl.substring(versionSeparator + 1);
canonicalUrl = canonicalUrl.substring(0, versionSeparator);
slice = myPackageVersionResourceDao.findCurrentVersionByCanonicalUrlAndVersion(PageRequest.of(0, 1), theFhirVersion, canonicalUrl, canonicalVersion);
} else {
slice = myPackageVersionResourceDao.findCurrentVersionByCanonicalUrl(PageRequest.of(0, 1), theFhirVersion, canonicalUrl);
}
if (slice.isEmpty()) {
return null;
} else {
NpmPackageVersionResourceEntity contents = slice.getContent().get(0);
ResourcePersistentId binaryPid = new ResourcePersistentId(contents.getResourceBinary().getId());
IBaseBinary binary = getBinaryDao().readByPid(binaryPid);
byte[] resourceContentsBytes = BinaryUtil.getOrCreateData(myCtx, binary).getValue();
String resourceContents = new String(resourceContentsBytes, StandardCharsets.UTF_8);
FhirContext packageContext = getFhirContext(contents.getFhirVersion());
return EncodingEnum.detectEncoding(resourceContents).newParser(packageContext).parseResource(resourceContents);
}
}
@Override
@Transactional
public NpmPackageMetadataJson loadPackageMetadata(String thePackageId) {
NpmPackageMetadataJson retVal = new NpmPackageMetadataJson();
Optional<NpmPackageEntity> pkg = myPackageDao.findByPackageId(thePackageId);
if (!pkg.isPresent()) {
throw new ResourceNotFoundException("Unknown package ID: " + thePackageId);
}
List<NpmPackageVersionEntity> packageVersions = new ArrayList<>(myPackageVersionDao.findByPackageId(thePackageId));
packageVersions.sort(new ReverseComparator<>((o1, o2) -> PackageVersionComparator.INSTANCE.compare(o1.getVersionId(), o2.getVersionId())));
for (NpmPackageVersionEntity next : packageVersions) {
if (next.isCurrentVersion()) {
retVal.setDistTags(new NpmPackageMetadataJson.DistTags().setLatest(next.getVersionId()));
}
NpmPackageMetadataJson.Version version = new NpmPackageMetadataJson.Version();
version.setFhirVersion(next.getFhirVersionId());
version.setDescription(next.getDescription());
version.setName(next.getPackageId());
version.setVersion(next.getVersionId());
version.setBytes(next.getPackageSizeBytes());
retVal.addVersion(version);
}
return retVal;
}
@Override
@Transactional
public PackageContents loadPackageContents(String thePackageId, String theVersion) {
Optional<NpmPackageVersionEntity> entity = loadPackageVersionEntity(thePackageId, theVersion);
return entity.map(t -> loadPackageContents(t)).orElse(null);
}
@Override
@Transactional
public NpmPackageSearchResultJson search(PackageSearchSpec thePackageSearchSpec) {
NpmPackageSearchResultJson retVal = new NpmPackageSearchResultJson();
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
// Query for total
{
CriteriaQuery<Long> countCriteriaQuery = cb.createQuery(Long.class);
Root<NpmPackageVersionEntity> countCriteriaRoot = countCriteriaQuery.from(NpmPackageVersionEntity.class);
countCriteriaQuery.multiselect(cb.countDistinct(countCriteriaRoot.get("myPackageId")));
List<Predicate> predicates = createSearchPredicates(thePackageSearchSpec, cb, countCriteriaRoot);
countCriteriaQuery.where(toPredicateArray(predicates));
Long total = myEntityManager.createQuery(countCriteriaQuery).getSingleResult();
retVal.setTotal(Math.toIntExact(total));
}
// Query for results
{
CriteriaQuery<NpmPackageVersionEntity> criteriaQuery = cb.createQuery(NpmPackageVersionEntity.class);
Root<NpmPackageVersionEntity> root = criteriaQuery.from(NpmPackageVersionEntity.class);
List<Predicate> predicates = createSearchPredicates(thePackageSearchSpec, cb, root);
criteriaQuery.where(toPredicateArray(predicates));
criteriaQuery.orderBy(cb.asc(root.get("myPackageId")));
TypedQuery<NpmPackageVersionEntity> query = myEntityManager.createQuery(criteriaQuery);
query.setFirstResult(thePackageSearchSpec.getStart());
query.setMaxResults(thePackageSearchSpec.getSize());
List<NpmPackageVersionEntity> resultList = query.getResultList();
for (NpmPackageVersionEntity next : resultList) {
if (!retVal.hasPackageWithId(next.getPackageId())) {
retVal
.addObject()
.getPackage()
.setName(next.getPackageId())
.setDescription(next.getPackage().getDescription())
.setVersion(next.getVersionId())
.addFhirVersion(next.getFhirVersionId())
.setBytes(next.getPackageSizeBytes());
} else {
NpmPackageSearchResultJson.Package retPackage = retVal.getPackageWithId(next.getPackageId());
retPackage.addFhirVersion(next.getFhirVersionId());
int cmp = PackageVersionComparator.INSTANCE.compare(next.getVersionId(), retPackage.getVersion());
if (cmp > 0) {
retPackage.setVersion(next.getVersionId());
}
}
}
}
return retVal;
}
@Override
@Transactional
public PackageDeleteOutcomeJson uninstallPackage(String thePackageId, String theVersion) {
PackageDeleteOutcomeJson retVal = new PackageDeleteOutcomeJson();
Optional<NpmPackageVersionEntity> packageVersion = myPackageVersionDao.findByPackageIdAndVersion(thePackageId, theVersion);
if (packageVersion.isPresent()) {
String msg = "Deleting package " + thePackageId + "#" + theVersion;
ourLog.info(msg);
retVal.getMessage().add(msg);
for (NpmPackageVersionResourceEntity next : packageVersion.get().getResources()) {
msg = "Deleting package +" + thePackageId + "#" + theVersion + "resource: " + next.getCanonicalUrl();
ourLog.info(msg);
retVal.getMessage().add(msg);
myPackageVersionResourceDao.delete(next);
ExpungeOptions options = new ExpungeOptions();
options.setExpungeDeletedResources(true).setExpungeOldVersions(true);
getBinaryDao().delete(next.getResourceBinary().getIdDt().toVersionless());
getBinaryDao().forceExpungeInExistingTransaction(next.getResourceBinary().getIdDt().toVersionless(), options, null);
}
myPackageVersionDao.delete(packageVersion.get());
ExpungeOptions options = new ExpungeOptions();
options.setExpungeDeletedResources(true).setExpungeOldVersions(true);
getBinaryDao().delete(packageVersion.get().getPackageBinary().getIdDt().toVersionless());
getBinaryDao().forceExpungeInExistingTransaction(packageVersion.get().getPackageBinary().getIdDt().toVersionless(), options, null);
Collection<NpmPackageVersionEntity> remainingVersions = myPackageVersionDao.findByPackageId(thePackageId);
if (remainingVersions.size() == 0) {
msg = "No versions of package " + thePackageId + " remain";
ourLog.info(msg);
retVal.getMessage().add(msg);
Optional<NpmPackageEntity> pkgEntity = myPackageDao.findByPackageId(thePackageId);
myPackageDao.delete(pkgEntity.get());
} else {
List<NpmPackageVersionEntity> versions = remainingVersions
.stream()
.sorted((o1, o2) -> PackageVersionComparator.INSTANCE.compare(o1.getVersionId(), o2.getVersionId()))
.collect(Collectors.toList());
for (int i = 0; i < versions.size(); i++) {
boolean isCurrent = i == versions.size() - 1;
if (isCurrent != versions.get(i).isCurrentVersion()) {
versions.get(i).setCurrentVersion(isCurrent);
myPackageVersionDao.save(versions.get(i));
}
}
}
} else {
String msg = "No package found with the given ID";
retVal.getMessage().add(msg);
}
return retVal;
}
@Nonnull
public List<Predicate> createSearchPredicates(PackageSearchSpec thePackageSearchSpec, CriteriaBuilder theCb, Root<NpmPackageVersionEntity> theRoot) {
List<Predicate> predicates = new ArrayList<>();
if (isNotBlank(thePackageSearchSpec.getResourceUrl())) {
Join<NpmPackageVersionEntity, NpmPackageVersionResourceEntity> resources = theRoot.join("myResources", JoinType.LEFT);
predicates.add(theCb.equal(resources.get("myCanonicalUrl").as(String.class), thePackageSearchSpec.getResourceUrl()));
}
if (isNotBlank(thePackageSearchSpec.getDescription())) {
String searchTerm = "%" + thePackageSearchSpec.getDescription() + "%";
searchTerm = StringUtil.normalizeStringForSearchIndexing(searchTerm);
predicates.add(theCb.like(theRoot.get("myDescriptionUpper").as(String.class), searchTerm));
}
if (isNotBlank(thePackageSearchSpec.getFhirVersion())) {
if (!thePackageSearchSpec.getFhirVersion().matches("([0-9]+\\.)+[0-9]+")) {
FhirVersionEnum versionEnum = FhirVersionEnum.forVersionString(thePackageSearchSpec.getFhirVersion());
if (versionEnum != null) {
predicates.add(theCb.equal(theRoot.get("myFhirVersion").as(FhirVersionEnum.class), versionEnum));
}
} else {
predicates.add(theCb.like(theRoot.get("myFhirVersionId").as(String.class), thePackageSearchSpec.getFhirVersion() + "%"));
}
}
return predicates;
}
@SuppressWarnings("unchecked")
public static List<String> getProcessingMessages(NpmPackage thePackage) {
return (List<String>) thePackage.getUserData().computeIfAbsent("JpPackageCache_ProcessingMessages", t -> new ArrayList<>());
}
}

View File

@ -0,0 +1,71 @@
package ca.uhn.fhir.jpa.packages;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable;
public class NpmJpaValidationSupport implements IValidationSupport {
@Autowired
private FhirContext myFhirContext;
@Autowired
private IHapiPackageCacheManager myHapiPackageCacheManager;
@Override
public FhirContext getFhirContext() {
return myFhirContext;
}
@Override
public IBaseResource fetchValueSet(String theUri) {
return fetchResource("ValueSet", theUri);
}
@Override
public IBaseResource fetchCodeSystem(String theUri) {
return fetchResource("CodeSystem", theUri);
}
@Override
public IBaseResource fetchStructureDefinition(String theUri) {
return fetchResource("StructureDefinition", theUri);
}
@Nullable
public IBaseResource fetchResource(String theResourceType, String theUri) {
FhirVersionEnum fhirVersion = myFhirContext.getVersion().getVersion();
IBaseResource asset = myHapiPackageCacheManager.loadPackageAssetByUrl(fhirVersion, theUri);
if (asset != null) {
Class<? extends IBaseResource> type = myFhirContext.getResourceDefinition(theResourceType).getImplementingClass();
if (type.isAssignableFrom(asset.getClass())) {
return asset;
}
}
return null;
}
}

View File

@ -0,0 +1,155 @@
package ca.uhn.fhir.jpa.packages;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.util.JsonDateDeserializer;
import ca.uhn.fhir.jpa.util.JsonDateSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.annotation.Nonnull;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@ApiModel("Represents an NPM package metadata response")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class NpmPackageMetadataJson {
@JsonProperty("dist-tags")
private DistTags myDistTags;
@JsonProperty("modified")
@JsonSerialize(using = JsonDateSerializer.class)
@JsonDeserialize(using = JsonDateDeserializer.class)
private Date myModified;
@JsonProperty("name")
private String myName;
@JsonProperty("versions")
private Map<String, Version> myVersionIdToVersion;
public void addVersion(Version theVersion) {
getVersions().put(theVersion.getVersion(), theVersion);
}
@Nonnull
public Map<String, Version> getVersions() {
if (myVersionIdToVersion == null) {
myVersionIdToVersion = new LinkedHashMap<>();
}
return myVersionIdToVersion;
}
public DistTags getDistTags() {
return myDistTags;
}
public void setDistTags(DistTags theDistTags) {
myDistTags = theDistTags;
}
public void setModified(Date theModified) {
myModified = theModified;
}
public void setName(String theName) {
myName = theName;
}
public static class DistTags {
@JsonProperty("latest")
private String myLatest;
public String getLatest() {
return myLatest;
}
public DistTags setLatest(String theLatest) {
myLatest = theLatest;
return this;
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public static class Version {
@JsonProperty("name")
private String myName;
@JsonProperty("version")
private String myVersion;
@JsonProperty("description")
private String myDescription;
@JsonProperty("fhirVersion")
private String myFhirVersion;
@ApiModelProperty(value = "The size of this package in bytes", example = "1000")
@JsonProperty("_bytes")
private long myBytes;
public String getName() {
return myName;
}
public void setName(String theName) {
myName = theName;
}
public String getDescription() {
return myDescription;
}
public void setDescription(String theDescription) {
myDescription = theDescription;
}
public String getFhirVersion() {
return myFhirVersion;
}
public void setFhirVersion(String theFhirVersion) {
myFhirVersion = theFhirVersion;
}
public String getVersion() {
return myVersion;
}
public void setVersion(String theVersion) {
myVersion = theVersion;
}
public long getBytes() {
return myBytes;
}
public void setBytes(long theBytes) {
myBytes = theBytes;
}
}
}

View File

@ -0,0 +1,153 @@
package ca.uhn.fhir.jpa.packages;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.ArrayList;
import java.util.List;
@ApiModel("Represents an NPM package search response")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class NpmPackageSearchResultJson {
@JsonProperty("objects")
private List<ObjectElement> myObjects;
@JsonProperty("total")
private int myTotal;
public List<ObjectElement> getObjects() {
if (myObjects == null) {
myObjects = new ArrayList<>();
}
return myObjects;
}
public ObjectElement addObject() {
ObjectElement object = new ObjectElement();
getObjects().add(object);
return object;
}
public int getTotal() {
return myTotal;
}
public void setTotal(int theTotal) {
myTotal = theTotal;
}
public boolean hasPackageWithId(String thePackageId) {
return getObjects().stream().anyMatch(t -> t.getPackage().getName().equals(thePackageId));
}
public Package getPackageWithId(String thePackageId) {
return getObjects().stream().map(t -> t.getPackage()).filter(t -> t.getName().equals(thePackageId)).findFirst().orElseThrow(() -> new IllegalArgumentException());
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public static class ObjectElement {
@JsonProperty("package")
private Package myPackage;
public Package getPackage() {
if (myPackage == null) {
myPackage = new Package();
}
return myPackage;
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public static class Package {
@JsonProperty("name")
private String myName;
@JsonProperty("version")
private String myVersion;
@JsonProperty("description")
private String myDescription;
@JsonProperty("fhirVersion")
private List<String> myFhirVersion;
@ApiModelProperty(value = "The size of this package in bytes", example = "1000")
@JsonProperty("_bytes")
private long myBytes;
public long getBytes() {
return myBytes;
}
public Package setBytes(long theBytes) {
myBytes = theBytes;
return this;
}
public String getName() {
return myName;
}
public Package setName(String theName) {
myName = theName;
return this;
}
public String getDescription() {
return myDescription;
}
public Package setDescription(String theDescription) {
myDescription = theDescription;
return this;
}
public List<String> getFhirVersion() {
if (myFhirVersion == null) {
myFhirVersion = new ArrayList<>();
}
return myFhirVersion;
}
public String getVersion() {
return myVersion;
}
public Package setVersion(String theVersion) {
myVersion = theVersion;
return this;
}
public Package addFhirVersion(String theFhirVersionId) {
if (!getFhirVersion().contains(theFhirVersionId)) {
getFhirVersion().add(theFhirVersionId);
getFhirVersion().sort(PackageVersionComparator.INSTANCE);
}
return this;
}
}
}

View File

@ -0,0 +1,46 @@
package ca.uhn.fhir.jpa.packages;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import java.util.ArrayList;
import java.util.List;
@ApiModel("Represents an NPM package deletion response")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class PackageDeleteOutcomeJson {
@JsonProperty("messages")
private List<String> myMessage;
public List<String> getMessage() {
if (myMessage == null) {
myMessage = new ArrayList<>();
}
return myMessage;
}
}

View File

@ -0,0 +1,46 @@
package ca.uhn.fhir.jpa.packages;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import java.util.ArrayList;
import java.util.List;
@ApiModel("Represents an NPM package installation response")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class PackageInstallOutcomeJson {
@JsonProperty("messages")
private List<String> myMessage;
public List<String> getMessage() {
if (myMessage == null) {
myMessage = new ArrayList<>();
}
return myMessage;
}
}

View File

@ -0,0 +1,196 @@
package ca.uhn.fhir.jpa.packages;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.model.api.annotation.ExampleSupplier;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
@ApiModel(
value = "PackageInstallationSpec",
description =
"Defines a set of instructions for package installation"
)
@JsonPropertyOrder({
"name", "version", "packageUrl", "installMode", "installResourceTypes", "validationMode"
})
@ExampleSupplier({PackageInstallationSpec.ExampleSupplier.class, PackageInstallationSpec.ExampleSupplier2.class})
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class PackageInstallationSpec {
@ApiModelProperty("The direct package URL")
@JsonProperty("packageUrl")
private String myPackageUrl;
@ApiModelProperty("The NPM package Name")
@JsonProperty("name")
private String myName;
@ApiModelProperty("The direct package version")
@JsonProperty("version")
private String myVersion;
@ApiModelProperty("Should resources from this package be extracted from the package and installed into the repository individually")
@JsonProperty("installMode")
private InstallModeEnum myInstallMode;
@ApiModelProperty("If resources are being installed individually, this is list provides the resource types to install. By default, all conformance resources will be installed.")
@JsonProperty("installResourceTypes")
private List<String> myInstallResourceTypes;
@ApiModelProperty("Should dependencies be automatically resolved, fetched and installed with the same settings")
@JsonProperty("fetchDependencies")
private boolean myFetchDependencies;
@ApiModelProperty("Any values provided here will be interpreted as a regex. Dependencies with an ID matching any regex will be skipped.")
private List<String> myDependencyExcludes;
@JsonIgnore
private byte[] myPackageContents;
public List<String> getDependencyExcludes() {
if (myDependencyExcludes == null) {
myDependencyExcludes = new ArrayList<>();
}
return myDependencyExcludes;
}
public void setDependencyExcludes(List<String> theDependencyExcludes) {
myDependencyExcludes = theDependencyExcludes;
}
public boolean isFetchDependencies() {
return myFetchDependencies;
}
public PackageInstallationSpec setFetchDependencies(boolean theFetchDependencies) {
myFetchDependencies = theFetchDependencies;
return this;
}
public String getPackageUrl() {
return myPackageUrl;
}
public PackageInstallationSpec setPackageUrl(String thePackageUrl) {
myPackageUrl = thePackageUrl;
return this;
}
public InstallModeEnum getInstallMode() {
return myInstallMode;
}
public PackageInstallationSpec setInstallMode(InstallModeEnum theInstallMode) {
myInstallMode = theInstallMode;
return this;
}
public List<String> getInstallResourceTypes() {
if (myInstallResourceTypes == null) {
myInstallResourceTypes = new ArrayList<>();
}
return myInstallResourceTypes;
}
public void setInstallResourceTypes(List<String> theInstallResourceTypes) {
myInstallResourceTypes = theInstallResourceTypes;
}
public String getName() {
return myName;
}
public PackageInstallationSpec setName(String theName) {
myName = theName;
return this;
}
public String getVersion() {
return myVersion;
}
public PackageInstallationSpec setVersion(String theVersion) {
myVersion = theVersion;
return this;
}
public byte[] getPackageContents() {
return myPackageContents;
}
public PackageInstallationSpec setPackageContents(byte[] thePackageContents) {
myPackageContents = thePackageContents;
return this;
}
public PackageInstallationSpec addDependencyExclude(String theExclude) {
getDependencyExcludes().add(theExclude);
return this;
}
public PackageInstallationSpec addInstallResourceTypes(String... theResourceTypes) {
for (String next : theResourceTypes) {
getInstallResourceTypes().add(next);
}
return this;
}
public enum InstallModeEnum {
STORE_ONLY,
STORE_AND_INSTALL
}
public enum ValidationModeEnum {
NOT_AVAILABLE,
AVAILABLE
}
public static class ExampleSupplier implements Supplier<PackageInstallationSpec> {
@Override
public PackageInstallationSpec get() {
return new PackageInstallationSpec()
.setName("hl7.fhir.us.core")
.setVersion("3.1.0")
.setInstallMode(InstallModeEnum.STORE_ONLY)
.setFetchDependencies(true);
}
}
public static class ExampleSupplier2 implements Supplier<PackageInstallationSpec> {
@Override
public PackageInstallationSpec get() {
return new PackageInstallationSpec()
.setName("com.example.my-resources")
.setVersion("1.0")
.setPackageUrl("classpath:/my-resources.tgz")
.setInstallMode(InstallModeEnum.STORE_AND_INSTALL)
.addInstallResourceTypes("Organization", "Medication", "PlanDefinition", "SearchParameter");
}
}
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.packages;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
@ -31,48 +32,60 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.util.FhirTerser;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.utilities.cache.IPackageCacheManager;
import org.hl7.fhir.utilities.cache.NpmPackage;
import org.hl7.fhir.utilities.cache.PackageCacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class IgInstallerSvc {
/**
* @since 5.1.0
*/
public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
private static final Logger ourLog = LoggerFactory.getLogger(IgInstallerSvc.class);
private static final Logger ourLog = LoggerFactory.getLogger(PackageInstallerSvcImpl.class);
public static List<String> DEFAULT_INSTALL_TYPES = Collections.unmodifiableList(Lists.newArrayList(
"NamingSystem",
"CodeSystem",
"ValueSet",
"StructureDefinition",
"ConceptMap",
"SearchParameter",
"Subscription"
));
boolean enabled = true;
@Autowired
private FhirContext fhirContext;
@Autowired
private DaoRegistry daoRegistry;
@Autowired
private IValidationSupport validationSupport;
@Autowired
private IHapiPackageCacheManager packageCacheManager;
private PackageCacheManager packageCacheManager;
private String[] SUPPORTED_RESOURCE_TYPES = new String[]
{ "NamingSystem",
"CodeSystem",
"ValueSet",
"StructureDefinition",
"ConceptMap",
"SearchParameter",
"Subscription" };
/**
* Constructor
*/
public PackageInstallerSvcImpl() {
super();
}
@PostConstruct
public void initialize() {
@ -90,91 +103,76 @@ public class IgInstallerSvc {
enabled = false;
}
}
try {
packageCacheManager = new PackageCacheManager(true, 1);
} catch (IOException e) {
ourLog.error("Unable to initialize PackageCacheManager: {}", e);
enabled = false;
}
}
/**
* Loads and installs an IG tarball (with its dependencies) from the specified url.
*
* Installs the IG by persisting instances of the following types of resources:
*
* - NamingSystem, CodeSystem, ValueSet, StructureDefinition (with snapshots),
* ConceptMap, SearchParameter, Subscription
*
* Creates the resources if non-existent, updates them otherwise.
*
* @param url of IG tarball
* @throws ImplementationGuideInstallationException if installation fails
*/
public void install(String url) throws ImplementationGuideInstallationException {
if (enabled) {
try {
install(NpmPackage.fromPackage(toInputStream(url)));
} catch (IOException e) {
ourLog.error("Could not load implementation guide from URL {}", url, e);
}
}
}
private InputStream toInputStream(String url) throws IOException {
URL u = new URL(url);
URLConnection c = u.openConnection();
return c.getInputStream();
}
/**
* Loads and installs an IG from a file on disk or the Simplifier repo using
* the {@link PackageCacheManager}.
*
* the {@link IPackageCacheManager}.
* <p>
* Installs the IG by persisting instances of the following types of resources:
*
* <p>
* - NamingSystem, CodeSystem, ValueSet, StructureDefinition (with snapshots),
* ConceptMap, SearchParameter, Subscription
*
* ConceptMap, SearchParameter, Subscription
* <p>
* Creates the resources if non-existent, updates them otherwise.
*
* @param id of the package, or name of folder in filesystem
* @param version of package, or path to folder in filesystem
* @throws ImplementationGuideInstallationException if installation fails
* @param theInstallationSpec The details about what should be installed
*/
public void install(String id, String version) throws ImplementationGuideInstallationException {
@Override
public PackageInstallOutcomeJson install(PackageInstallationSpec theInstallationSpec) throws ImplementationGuideInstallationException {
PackageInstallOutcomeJson retVal = new PackageInstallOutcomeJson();
if (enabled) {
try {
install(packageCacheManager.loadPackage(id, version));
NpmPackage npmPackage = packageCacheManager.installPackage(theInstallationSpec);
if (npmPackage == null) {
throw new IOException("Package not found");
}
retVal.getMessage().addAll(JpaPackageCache.getProcessingMessages(npmPackage));
if (theInstallationSpec.isFetchDependencies()) {
fetchAndInstallDependencies(npmPackage, theInstallationSpec, retVal);
}
if (theInstallationSpec.getInstallMode() == PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL) {
install(npmPackage, theInstallationSpec);
}
} catch (IOException e) {
ourLog.error("Could not load implementation guide from packages.fhir.org or " +
"file on disk using ID {} and version {}", id, version, e);
throw new ImplementationGuideInstallationException("Could not load NPM package " + theInstallationSpec.getName() + "#" + theInstallationSpec.getVersion(), e);
}
}
return retVal;
}
/**
* Installs a package and its dependencies.
*
* <p>
* Fails fast if one of its dependencies could not be installed.
*
* @throws ImplementationGuideInstallationException if installation fails
*/
private void install(NpmPackage npmPackage) throws ImplementationGuideInstallationException {
private void install(NpmPackage npmPackage, PackageInstallationSpec theInstallationSpec) throws ImplementationGuideInstallationException {
String name = npmPackage.getNpm().get("name").getAsString();
String version = npmPackage.getNpm().get("version").getAsString();
String fhirVersion = npmPackage.fhirVersion();
String currentFhirVersion = fhirContext.getVersion().getVersion().getFhirVersionString();
assertFhirVersionsAreCompatible(fhirVersion, currentFhirVersion);
fetchAndInstallDependencies(npmPackage);
List<String> installTypes;
if (!theInstallationSpec.getInstallResourceTypes().isEmpty()) {
installTypes = theInstallationSpec.getInstallResourceTypes();
} else {
installTypes = DEFAULT_INSTALL_TYPES;
}
ourLog.info("Installing package: {}#{}", name, version);
int[] count = new int[SUPPORTED_RESOURCE_TYPES.length];
int[] count = new int[installTypes.size()];
for (int i = 0; i < SUPPORTED_RESOURCE_TYPES.length; i++) {
Collection<IBaseResource> resources = parseResourcesOfType(SUPPORTED_RESOURCE_TYPES[i], npmPackage);
for (int i = 0; i < installTypes.size(); i++) {
Collection<IBaseResource> resources = parseResourcesOfType(installTypes.get(i), npmPackage);
count[i] = resources.size();
try {
@ -182,37 +180,48 @@ public class IgInstallerSvc {
.map(r -> isStructureDefinitionWithoutSnapshot(r) ? generateSnapshot(r) : r)
.forEach(r -> createOrUpdate(r));
} catch (Exception e) {
throw new ImplementationGuideInstallationException(String.format(
"Error installing IG %s#%s: ", name, version), e);
throw new ImplementationGuideInstallationException(String.format("Error installing IG %s#%s: %s", name, version, e.toString()), e);
}
}
ourLog.info(String.format("Finished installation of package %s#%s:", name, version));
for (int i = 0; i < count.length; i++) {
ourLog.info(String.format("-- Created or updated %s resources of type %s", count[i], SUPPORTED_RESOURCE_TYPES[i]));
ourLog.info(String.format("-- Created or updated %s resources of type %s", count[i], installTypes.get(i)));
}
}
private void fetchAndInstallDependencies(NpmPackage npmPackage) throws ImplementationGuideInstallationException {
private void fetchAndInstallDependencies(NpmPackage npmPackage, PackageInstallationSpec theInstallationSpec, PackageInstallOutcomeJson theOutcome) throws ImplementationGuideInstallationException {
if (npmPackage.getNpm().has("dependencies")) {
Map<String, String> dependencies = new Gson().fromJson(npmPackage.getNpm().get("dependencies"), HashMap.class);
JsonElement dependenciesElement = npmPackage.getNpm().get("dependencies");
Map<String, String> dependencies = new Gson().fromJson(dependenciesElement, HashMap.class);
for (Map.Entry<String, String> d : dependencies.entrySet()) {
String id = d.getKey();
String ver = d.getValue();
if (id.startsWith("hl7.fhir")) {
continue; // todo : which packages to ignore?
}
if (packageCacheManager == null) {
throw new ImplementationGuideInstallationException(String.format(
"Cannot install dependency %s#%s due to PacketCacheManager initialization error", id, ver));
}
try {
theOutcome.getMessage().add("Package " + npmPackage.id() + "#" + npmPackage.version() + " depends on package " + id + "#" + ver);
boolean skip = false;
for (String next : theInstallationSpec.getDependencyExcludes()) {
if (id.matches(next)) {
theOutcome.getMessage().add("Not installing dependency " + id + " because it matches exclude criteria: " + next);
skip = true;
break;
}
}
if (skip) {
continue;
}
// resolve in local cache or on packages.fhir.org
NpmPackage dependency = packageCacheManager.loadPackage(id, ver);
// recursive call to install dependencies of a package before
// installing the package
fetchAndInstallDependencies(dependency);
install(dependency);
fetchAndInstallDependencies(dependency, theInstallationSpec, theOutcome);
if (theInstallationSpec.getInstallMode() == PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL) {
install(dependency, theInstallationSpec);
}
} catch (IOException e) {
throw new ImplementationGuideInstallationException(String.format(
"Cannot resolve dependency %s#%s", id, ver), e);
@ -228,8 +237,11 @@ public class IgInstallerSvc {
private void assertFhirVersionsAreCompatible(String fhirVersion, String currentFhirVersion)
throws ImplementationGuideInstallationException {
boolean compatible = fhirVersion.charAt(0) == currentFhirVersion.charAt(0) &&
currentFhirVersion.compareTo(fhirVersion) >= 0;
FhirVersionEnum fhirVersionEnum = FhirVersionEnum.forVersionString(fhirVersion);
FhirVersionEnum currentFhirVersionEnum = FhirVersionEnum.forVersionString(currentFhirVersion);
Validate.notNull(fhirVersionEnum, "Invalid FHIR version string: %s", fhirVersion);
Validate.notNull(currentFhirVersionEnum, "Invalid FHIR version string: %s", currentFhirVersion);
boolean compatible = fhirVersionEnum.equals(currentFhirVersionEnum);
if (!compatible) {
throw new ImplementationGuideInstallationException(String.format(
"Cannot install implementation guide: FHIR versions mismatch (expected <=%s, package uses %s)",
@ -292,7 +304,6 @@ public class IgInstallerSvc {
}
private SearchParameterMap createSearchParameterMapFor(IBaseResource resource) {
FhirTerser terser = fhirContext.newTerser();
if (resource.getClass().getSimpleName().equals("NamingSystem")) {
String uniqueId = extractUniqeIdFromNamingSystem(resource);
return new SearchParameterMap().add("value", new StringParam(uniqueId).setExact(true));
@ -332,16 +343,6 @@ public class IgInstallerSvc {
}
}
private static IBaseResource getFirstResourceFrom(IBundleProvider searchResult) {
try {
return searchResult.getResources(0, 0).get(0);
} catch (IndexOutOfBoundsException e) {
ourLog.warn("Error when extracting resource from search result " +
"(search result should have been non-empty))", e);
return null;
}
}
private String extractUniqeIdFromNamingSystem(IBaseResource resource) {
FhirTerser terser = fhirContext.newTerser();
IBase uniqueIdComponent = (IBase) terser.getSingleValueOrNull(resource, "uniqueId");
@ -363,4 +364,14 @@ public class IgInstallerSvc {
IPrimitiveType asPrimitiveType = (IPrimitiveType) terser.getSingleValueOrNull(resource, "url");
return (String) asPrimitiveType.getValue();
}
private static IBaseResource getFirstResourceFrom(IBundleProvider searchResult) {
try {
return searchResult.getResources(0, 0).get(0);
} catch (IndexOutOfBoundsException e) {
ourLog.warn("Error when extracting resource from search result " +
"(search result should have been non-empty))", e);
return null;
}
}
}

View File

@ -0,0 +1,73 @@
package ca.uhn.fhir.jpa.packages;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.apache.commons.lang3.Validate;
public class PackageSearchSpec {
private int myStart;
private int mySize = 50;
private String myResourceUrl;
private CharSequence myDescription;
private String myFhirVersion;
public String getFhirVersion() {
return myFhirVersion;
}
public void setFhirVersion(String theFhirVersion) {
myFhirVersion = theFhirVersion;
}
public int getSize() {
return mySize;
}
public void setSize(int theSize) {
Validate.inclusiveBetween(1, 250, theSize, "Number must be between 1-250");
mySize = theSize;
}
public int getStart() {
return myStart;
}
public void setStart(int theStart) {
Validate.inclusiveBetween(0, Integer.MAX_VALUE, theStart, "Number must not be negative");
myStart = theStart;
}
public String getResourceUrl() {
return myResourceUrl;
}
public void setResourceUrl(String theResourceUrl) {
myResourceUrl = theResourceUrl;
}
public CharSequence getDescription() {
return myDescription;
}
public void setDescription(CharSequence theDescription) {
myDescription = theDescription;
}
}

View File

@ -0,0 +1,73 @@
package ca.uhn.fhir.jpa.packages;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.math.BigDecimal;
import java.util.Comparator;
import static org.apache.commons.lang3.StringUtils.isNumeric;
public class PackageVersionComparator implements Comparator<String> {
public static final PackageVersionComparator INSTANCE = new PackageVersionComparator();
@Override
public int compare(String o1, String o2) {
String[] o1parts = o1.split("\\.");
String[] o2parts = o2.split("\\.");
for (int i = 0; i < o1parts.length && i < o2parts.length; i++) {
String i1part = o1parts[i];
String i2part = o2parts[i];
if (isNumeric(i1part)) {
if (isNumeric(i2part)) {
int cmp = new BigDecimal(i1part).compareTo(new BigDecimal(i2part));
if (cmp != 0) {
return cmp;
}
}
}
int cmp = i1part.compareTo(i2part);
if (cmp != 0) {
return cmp;
}
}
return o1parts.length - o2parts.length;
}
public static boolean isEquivalent(String theSpec, String thePackageVersion) {
String[] o1parts = theSpec.split("\\.");
String[] o2parts = thePackageVersion.split("\\.");
for (int i = 0; i < o1parts.length; i++ ) {
if (!o1parts[i].equals("x")) {
if (!o1parts[i].equals(o2parts[i])) {
return false;
}
}
}
return true;
}
}

View File

@ -10,6 +10,8 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static ca.uhn.fhir.util.StringUtil.toUtf8String;
/*
* #%L
* HAPI FHIR JPA Server
@ -46,7 +48,7 @@ public class XmlPatchUtils {
throw new InternalErrorException(e);
}
String resultString = new String(result.toByteArray(), Constants.CHARSET_UTF8);
String resultString = toUtf8String(result.toByteArray());
T retVal = theCtx.newXmlParser().parseResource(clazz, resultString);
return retVal;

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
@ -173,8 +174,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseTermReadSvcImpl.class);
private static final ValueSetExpansionOptions DEFAULT_EXPANSION_OPTIONS = new ValueSetExpansionOptions();
private static final TermCodeSystemVersion NO_CURRENT_VERSION = new TermCodeSystemVersion().setId(-1L);
private static boolean ourLastResultsFromTranslationCache; // For testing.
private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing.
private final int myFetchSize = DEFAULT_FETCH_SIZE;
private final Cache<String, TermCodeSystemVersion> myCodeSystemCurrentVersionCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
@Autowired
protected DaoRegistry myDaoRegistry;
@Autowired
@ -209,7 +213,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
private DaoConfig myDaoConfig;
private Cache<TranslationQuery, List<TermConceptMapGroupElementTarget>> myTranslationCache;
private Cache<TranslationQuery, List<TermConceptMapGroupElement>> myTranslationWithReverseCache;
private final int myFetchSize = DEFAULT_FETCH_SIZE;
private TransactionTemplate myTxTemplate;
@Autowired
private PlatformTransactionManager myTransactionManager;
@ -227,12 +230,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
private ITermCodeSystemStorageSvc myConceptStorageSvc;
@Autowired
private ApplicationContext myApplicationContext;
@Autowired
private DefaultProfileValidationSupport myDefaultProfileValidationSupport;
private volatile IValidationSupport myJpaValidationSupport;
private volatile IValidationSupport myValidationSupport;
@Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
return supportsSystem(theSystem);
TermCodeSystemVersion cs = getCurrentCodeSystemVersion(theSystem);
return cs != null;
}
private void addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) {
@ -283,16 +289,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
* This method is present only for unit tests, do not call from client code
*/
@VisibleForTesting
public void clearTranslationCache() {
public void clearCaches() {
myTranslationCache.invalidateAll();
}
/**
* This method is present only for unit tests, do not call from client code
*/
@VisibleForTesting()
public void clearTranslationWithReverseCache() {
myTranslationWithReverseCache.invalidateAll();
myCodeSystemCurrentVersionCache.invalidateAll();
}
public void deleteConceptMap(ResourceTable theResourceTable) {
@ -1289,15 +1289,35 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY);
return txTemplate.execute(t -> {
TermCodeSystemVersion csv = null;
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theCodeSystem);
if (cs != null && cs.getCurrentVersion() != null) {
csv = cs.getCurrentVersion();
TermCodeSystemVersion csv = getCurrentCodeSystemVersion(theCodeSystem);
if (csv == null) {
return null;
}
return myConceptDao.findByCodeSystemAndCode(csv, theCode);
});
}
@Nullable
private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri) {
TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(theUri, uri -> {
TermCodeSystemVersion csv = null;
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(uri);
if (cs != null && cs.getCurrentVersion() != null) {
csv = cs.getCurrentVersion();
}
if (csv != null) {
return csv;
} else {
return NO_CURRENT_VERSION;
}
});
if (retVal == NO_CURRENT_VERSION) {
return null;
}
return retVal;
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public Set<TermConcept> findCodesAbove(Long theCodeSystemResourcePid, Long theCodeSystemVersionPid, String theCode) {
@ -1715,12 +1735,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return null;
}
@Override
public boolean supportsSystem(String theSystem) {
TermCodeSystem cs = getCodeSystem(theSystem);
return cs != null;
}
private ArrayList<VersionIndependentConcept> toVersionIndependentConcepts(String theSystem, Set<TermConcept> codes) {
ArrayList<VersionIndependentConcept> retVal = new ArrayList<>(codes.size());
for (TermConcept next : codes) {

View File

@ -94,8 +94,6 @@ public interface ITermReadSvc extends IValidationSupport {
void storeTermValueSet(ResourceTable theResourceTable, ValueSet theValueSet);
boolean supportsSystem(String theCodeSystem);
List<TermConceptMapGroupElementTarget> translate(TranslationRequest theTranslationRequest);
List<TermConceptMapGroupElement> translateWithReverse(TranslationRequest theTranslationRequest);

View File

@ -180,18 +180,21 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
/**
* Log all captured UPDATE queries
*/
public void logUpdateQueriesForCurrentThread() {
public String logUpdateQueriesForCurrentThread() {
List<String> queries = getUpdateQueriesForCurrentThread()
.stream()
.map(CircularQueueCaptureQueriesListener::formatQueryAsSql)
.collect(Collectors.toList());
ourLog.info("Update Queries:\n{}", String.join("\n", queries));
String joined = String.join("\n", queries);
ourLog.info("Update Queries:\n{}", joined);
return joined;
}
/**
* Log all captured SELECT queries
* @return
*/
public void logSelectQueriesForCurrentThread(int... theIndexes) {
public String logSelectQueriesForCurrentThread(int... theIndexes) {
List<String> queries = getSelectQueriesForCurrentThread()
.stream()
.map(CircularQueueCaptureQueriesListener::formatQueryAsSql)
@ -205,7 +208,9 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
queries = newList;
}
ourLog.info("Select Queries:\n{}", String.join("\n", queries));
String queriesAsString = String.join("\n", queries);
ourLog.info("Select Queries:\n{}", queriesAsString);
return queriesAsString;
}
/**
@ -234,12 +239,14 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
/**
* Log all captured INSERT queries
*/
public void logInsertQueriesForCurrentThread() {
public String logInsertQueriesForCurrentThread() {
List<String> queries = getInsertQueriesForCurrentThread()
.stream()
.map(CircularQueueCaptureQueriesListener::formatQueryAsSql)
.collect(Collectors.toList());
ourLog.info("Insert Queries:\n{}", String.join("\n", queries));
String queriesAsString = String.join("\n", queries);
ourLog.info("Insert Queries:\n{}", queriesAsString);
return queriesAsString;
}
/**
@ -278,14 +285,17 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
/**
* Log all captured DELETE queries
*/
public void logDeleteQueriesForCurrentThread() {
public String logDeleteQueriesForCurrentThread() {
List<String> queries = getDeleteQueriesForCurrentThread()
.stream()
.map(CircularQueueCaptureQueriesListener::formatQueryAsSql)
.collect(Collectors.toList());
ourLog.info("Delete Queries:\n{}", String.join("\n", queries));
String joined = String.join("\n", queries);
ourLog.info("Delete Queries:\n{}", joined);
return joined;
}
/**
* Log all captured DELETE queries
*/

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.packages.NpmJpaValidationSupport;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
@ -46,6 +47,8 @@ public class JpaValidationSupportChain extends ValidationSupportChain {
private IValidationSupport myDefaultProfileValidationSupport;
@Autowired
private ITermReadSvc myTerminologyService;
@Autowired
private NpmJpaValidationSupport myNpmJpaValidationSupport;
public JpaValidationSupportChain(FhirContext theFhirContext) {
myFhirContext = theFhirContext;
@ -63,12 +66,13 @@ public class JpaValidationSupportChain extends ValidationSupportChain {
@PostConstruct
public void postConstruct() {
addValidationSupport((IValidationSupport) new CommonCodeSystemsTerminologyService(myFhirContext));
addValidationSupport(new CommonCodeSystemsTerminologyService(myFhirContext));
addValidationSupport(myDefaultProfileValidationSupport);
addValidationSupport(myJpaValidationSupport);
addValidationSupport((IValidationSupport) myTerminologyService);
addValidationSupport((IValidationSupport) new SnapshotGeneratingValidationSupport(myFhirContext));
addValidationSupport((IValidationSupport) new InMemoryTerminologyServerValidationSupport(myFhirContext));
addValidationSupport(myTerminologyService);
addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext));
addValidationSupport(new InMemoryTerminologyServerValidationSupport(myFhirContext));
addValidationSupport(myNpmJpaValidationSupport);
}
}

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
@ -152,12 +153,12 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
*/
@Bean
@Lazy
public RequestValidatingInterceptor requestValidatingInterceptor() {
public RequestValidatingInterceptor requestValidatingInterceptor(IInstanceValidatorModule theInstanceValidator) {
RequestValidatingInterceptor requestValidator = new RequestValidatingInterceptor();
requestValidator.setFailOnSeverity(ResultSeverityEnum.ERROR);
requestValidator.setAddResponseHeaderOnSeverity(null);
requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
requestValidator.addValidatorModule(instanceValidator());
requestValidator.addValidatorModule(theInstanceValidator);
return requestValidator;
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
@ -8,6 +9,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.bulk.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.TermConcept;
@ -30,6 +32,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.test.utilities.LoggingRule;
import ca.uhn.fhir.test.utilities.ProxyUtil;
import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.StopWatch;
@ -54,6 +57,8 @@ import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.PlatformTransactionManager;
@ -125,6 +130,10 @@ public abstract class BaseJpaTest extends BaseTest {
private IdHelperService myIdHelperService;
@Autowired
private MemoryCacheService myMemoryCacheService;
@Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT)
@Autowired
private IValidationSupport myJpaPersistedValidationSupport;
@After
public void afterPerformCleanup() {
@ -138,6 +147,11 @@ public abstract class BaseJpaTest extends BaseTest {
if (myMemoryCacheService != null) {
myMemoryCacheService.invalidateAllCaches();
}
if (myJpaPersistedValidationSupport != null) {
ProxyUtil.getSingletonTarget(myJpaPersistedValidationSupport, JpaPersistedResourceValidationSupport.class).clearCaches();
}
}
@After
@ -423,11 +437,15 @@ public abstract class BaseJpaTest extends BaseTest {
}
public static String loadClasspath(String resource) throws IOException {
return new String(loadClasspathBytes(resource), Constants.CHARSET_UTF8);
}
public static byte[] loadClasspathBytes(String resource) throws IOException {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream(resource);
if (bundleRes == null) {
throw new NullPointerException("Can not load " + resource);
}
return IOUtils.toString(bundleRes, Constants.CHARSET_UTF8);
return IOUtils.toByteArray(bundleRes);
}
protected static void purgeDatabase(DaoConfig theDaoConfig, IFhirSystemDao<?, ?> theSystemDao, IResourceReindexingSvc theResourceReindexingSvc, ISearchCoordinatorSvc theSearchCoordinatorSvc, ISearchParamRegistry theSearchParamRegistry, IBulkDataExportSvc theBulkDataExportSvc) {

View File

@ -121,6 +121,7 @@ import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.EntityManager;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static org.hl7.fhir.convertors.conv30_40.ConceptMap30_40.convertConceptMap;
@ -356,8 +357,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@After
public void afterClearTerminologyCaches() {
BaseTermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc);
baseHapiTerminologySvc.clearTranslationCache();
baseHapiTerminologySvc.clearTranslationWithReverseCache();
baseHapiTerminologySvc.clearCaches();
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationCache();
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache();
TermDeferredStorageSvcImpl deferredSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc);
@ -412,7 +412,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
if (stream == null) {
fail("Unable to load resource: " + resourceName);
}
String string = IOUtils.toString(stream, "UTF-8");
String string = IOUtils.toString(stream, StandardCharsets.UTF_8);
IParser newJsonParser = EncodingEnum.detectEncodingNoDefault(string).newParser(myFhirCtx);
return newJsonParser.parseResource(type, string);
}

View File

@ -1,5 +1,8 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
@ -7,6 +10,7 @@ import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.test.utilities.ProxyUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.validation.IValidatorModule;
import org.apache.commons.io.IOUtils;
@ -23,6 +27,7 @@ import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.util.AopTestUtils;
import java.io.IOException;
@ -40,6 +45,9 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
private CachingValidationSupport myValidationSupport;
@Autowired
private FhirInstanceValidator myFhirInstanceValidator;
@Autowired
@Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT)
private IValidationSupport myPersistedResourceValidationSupport;
@Test
public void testValidateChangedQuestionnaire() {
@ -78,6 +86,7 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
ourLog.info("Clearing cache");
myValidationSupport.invalidateCaches();
myFhirInstanceValidator.invalidateCaches();
ProxyUtil.getSingletonTarget(myPersistedResourceValidationSupport, JpaPersistedResourceValidationSupport.class).clearCaches();
try {
myQuestionnaireResponseDao.validate(qr, null, null, null, null, null, null);

View File

@ -484,8 +484,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBui
@After
public void afterClearTerminologyCaches() {
BaseTermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc);
baseHapiTerminologySvc.clearTranslationCache();
baseHapiTerminologySvc.clearTranslationWithReverseCache();
baseHapiTerminologySvc.clearCaches();
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationCache();
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache();
TermDeferredStorageSvcImpl termDeferredStorageSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc);

View File

@ -9,11 +9,16 @@ import ca.uhn.fhir.rest.param.ReferenceParam;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.hl7.fhir.r4.model.StringType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@ -25,7 +30,6 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.contains;
public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class);
@ -114,6 +118,52 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
}
@Test
public void testValidate() {
CodeSystem cs = new CodeSystem();
cs.setUrl("http://foo/cs");
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.addConcept().setCode("bar-1").setDisplay("Bar 1");
cs.addConcept().setCode("bar-2").setDisplay("Bar 2");
myCodeSystemDao.create(cs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(cs));
Observation obs = new Observation();
// obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED).setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(Observation.ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
obs.getCode().addCoding().setSystem("http://foo/cs").setCode("bar-1");
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
// Validate once
myCaptureQueriesListener.clear();
myObservationDao.validate(obs, null, null, null, null, null, null);
assertEquals(myCaptureQueriesListener.logSelectQueriesForCurrentThread(), 10, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(myCaptureQueriesListener.logUpdateQueriesForCurrentThread(), 0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(myCaptureQueriesListener.logInsertQueriesForCurrentThread(), 0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
assertEquals(myCaptureQueriesListener.logDeleteQueriesForCurrentThread(), 0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
// Validate again (should rely only on caches)
myCaptureQueriesListener.clear();
myObservationDao.validate(obs, null, null, null, null, null, null);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
}
@Test
public void testVRead() {
IIdType id = runInTransaction(() -> {
@ -488,12 +538,10 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
}
@Test
public void testTransactionWithMultipleReferences() {
Bundle input = new Bundle();
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.setActive(true);
@ -1079,8 +1127,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, input);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(8, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(myCaptureQueriesListener.logSelectQueriesForCurrentThread(), 3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(myCaptureQueriesListener.logInsertQueriesForCurrentThread(), 8, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());

View File

@ -136,6 +136,27 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "Unknown code 'http://terminology.hl7.org/CodeSystem/observation-category#FOO'", oo.getIssueFirstRep().getDiagnostics());
// Make sure we're caching the validations as opposed to hitting the DB every time
myCaptureQueriesListener.clear();
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCoding().clear();
obs.getCategory().clear();
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE4").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
myCaptureQueriesListener.clear();
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE4").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
}
/**

View File

@ -429,8 +429,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest {
@After
public void afterClearTerminologyCaches() {
BaseTermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc);
baseHapiTerminologySvc.clearTranslationCache();
baseHapiTerminologySvc.clearTranslationWithReverseCache();
baseHapiTerminologySvc.clearCaches();
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationCache();
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache();
TermDeferredStorageSvcImpl deferredStorageSvc = AopTestUtils.getTargetObject(myTermDeferredStorageSvc);

View File

@ -1,53 +1,215 @@
package ca.uhn.fhir.jpa.packages;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import org.hl7.fhir.utilities.cache.PackageCacheManager;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.ProxyUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.utilities.cache.IPackageCacheManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.io.InputStream;
import java.util.stream.Collectors;
import static ca.uhn.fhir.util.ClasspathUtil.loadResourceAsByteArray;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class IgInstallerTestDstu3 extends BaseJpaDstu3Test {
private static final Logger ourLog = LoggerFactory.getLogger(IgInstallerTestDstu3.class);
@Autowired
private DaoConfig daoConfig;
@Autowired
private IgInstallerSvc igInstaller;
@Rule
public final ExpectedException expectedException = ExpectedException.none();
private PackageInstallerSvcImpl igInstaller;
@Autowired
private IPackageCacheManager myPackageCacheManager;
private Server myServer;
private NpmTestR4.FakeNpmServlet myFakeNpmServlet;
@Autowired
private INpmPackageVersionDao myPackageVersionDao;
private int myPort;
@Before
public void before() throws IOException {
PackageCacheManager packageCacheManager = new PackageCacheManager(true, 1);
public void before() throws Exception {
JpaPackageCache jpaPackageCache = ProxyUtil.getSingletonTarget(myPackageCacheManager, JpaPackageCache.class);
myServer = new Server(0);
ServletHandler proxyHandler = new ServletHandler();
myFakeNpmServlet = new NpmTestR4.FakeNpmServlet();
ServletHolder servletHolder = new ServletHolder(myFakeNpmServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
myServer.setHandler(proxyHandler);
myServer.start();
myPort = JettyUtil.getPortForStartedServer(myServer);
jpaPackageCache.getPackageServers().clear();
jpaPackageCache.addPackageServer("http://localhost:" + myPort);
myFakeNpmServlet.getResponses().clear();
InputStream stream;
stream = IgInstallerTestDstu3.class.getResourceAsStream("erroneous-ig.tar.gz");
packageCacheManager.addPackageToCache("erroneous-ig", "1.0.0", stream, "erroneous-ig");
stream = IgInstallerTestDstu3.class.getResourceAsStream("NHSD.Assets.STU3.tar.gz");
packageCacheManager.addPackageToCache("NHSD.Assets.STU3", "1.0.0", stream, "NHSD.Assets.STU3");
stream = IgInstallerTestDstu3.class.getResourceAsStream("basisprofil.de.tar.gz");
packageCacheManager.addPackageToCache("basisprofil.de", "0.2.40", stream, "basisprofil.de");
}
@Test(expected = ImplementationGuideInstallationException.class)
public void negativeTestInstallFromCache() {
// Unknown base of StructureDefinitions
igInstaller.install("erroneous-ig", "1.0.0");
@After
public void after() throws Exception {
JettyUtil.closeServer(myServer);
daoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
}
@Test
public void installFromCache() {
public void testNegativeInstallFromCache() {
daoConfig.setAllowExternalReferences(true);
igInstaller.install("NHSD.Assets.STU3", "1.2.0");
byte[] bytes = loadResourceAsByteArray("/packages/erroneous-ig.tar.gz");
// Unknown base of StructureDefinitions
try {
igInstaller.install(new PackageInstallationSpec().setName("erroneous-ig").setVersion("1.0.2").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL).setPackageContents(bytes));
fail();
} catch (ImplementationGuideInstallationException e) {
Assert.assertThat(e.getMessage(), containsString("Failure when generating snapshot of StructureDefinition"));
}
}
@Test
public void testInstallPackageWithDependencies() {
byte[] bytes;
bytes = loadResourceAsByteArray("/packages/nictiz.fhir.nl.stu3.questionnaires-1.0.2.tgz");
myFakeNpmServlet.getResponses().put("/nictiz.fhir.nl.stu3.questionnaires/1.0.2", bytes);
bytes = loadResourceAsByteArray("/packages/nictiz.fhir.nl.stu3.zib2017-1.3.10.tgz");
myFakeNpmServlet.getResponses().put("/nictiz.fhir.nl.stu3.zib2017/1.3.x", bytes);
daoConfig.setAllowExternalReferences(true);
PackageInstallationSpec spec = new PackageInstallationSpec()
.setName("nictiz.fhir.nl.stu3.questionnaires")
.setVersion("1.0.2")
.setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)
.setFetchDependencies(true)
.addDependencyExclude("hl7\\.fhir\\.[a-zA-Z0-9]+\\.core");
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
ourLog.info("Install messages:\n * {}", outcome.getMessage().stream().collect(Collectors.joining("\n * ")));
assertThat(outcome.getMessage(), hasItem("Indexing Resource[package/vl-QuestionnaireProvisioningTask.json] with URL: http://nictiz.nl/fhir/StructureDefinition/vl-QuestionnaireProvisioningTask|1.0.1"));
runInTransaction(() -> {
assertTrue(myPackageVersionDao.findByPackageIdAndVersion("nictiz.fhir.nl.stu3.questionnaires", "1.0.2").isPresent());
assertTrue(myPackageVersionDao.findByPackageIdAndVersion("nictiz.fhir.nl.stu3.zib2017", "1.3.10").isPresent());
});
}
@Test
public void testInstallPackageWithoutDependencies() {
byte[] bytes;
bytes = loadResourceAsByteArray("/packages/nictiz.fhir.nl.stu3.questionnaires-1.0.2.tgz");
myFakeNpmServlet.getResponses().put("/nictiz.fhir.nl.stu3.questionnaires/1.0.2", bytes);
bytes = loadResourceAsByteArray("/packages/nictiz.fhir.nl.stu3.zib2017-1.3.10.tgz");
myFakeNpmServlet.getResponses().put("/nictiz.fhir.nl.stu3.zib2017/1.3.x", bytes);
daoConfig.setAllowExternalReferences(true);
igInstaller.install(new PackageInstallationSpec().setName("nictiz.fhir.nl.stu3.questionnaires").setVersion("1.0.2").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL).setFetchDependencies(false));
runInTransaction(() -> {
assertTrue(myPackageVersionDao.findByPackageIdAndVersion("nictiz.fhir.nl.stu3.questionnaires", "1.0.2").isPresent());
assertFalse(myPackageVersionDao.findByPackageIdAndVersion("nictiz.fhir.nl.stu3.zib2017", "1.3.10").isPresent());
});
}
@Test
public void testInstallPackageByUrl_Http() {
byte[] bytes;
bytes = loadResourceAsByteArray("/packages/nictiz.fhir.nl.stu3.questionnaires-1.0.2.tgz");
myFakeNpmServlet.getResponses().put("/foo.tgz", bytes);
igInstaller.install(new PackageInstallationSpec()
.setName("nictiz.fhir.nl.stu3.questionnaires")
.setVersion("1.0.2")
.setPackageUrl("http://localhost:" + myPort + "/foo.tgz")
);
runInTransaction(() -> {
assertTrue(myPackageVersionDao.findByPackageIdAndVersion("nictiz.fhir.nl.stu3.questionnaires", "1.0.2").isPresent());
});
}
@Test
public void testInstallPackageByUrl_Classpath() {
byte[] bytes;
igInstaller.install(new PackageInstallationSpec()
.setName("nictiz.fhir.nl.stu3.questionnaires")
.setVersion("1.0.2")
.setPackageUrl("classpath:/packages/nictiz.fhir.nl.stu3.questionnaires-1.0.2.tgz")
);
runInTransaction(() -> {
assertTrue(myPackageVersionDao.findByPackageIdAndVersion("nictiz.fhir.nl.stu3.questionnaires", "1.0.2").isPresent());
});
}
@Test
public void testInstallPackageByUrl_WrongPackageId() {
byte[] bytes;
bytes = loadResourceAsByteArray("/packages/nictiz.fhir.nl.stu3.questionnaires-1.0.2.tgz");
myFakeNpmServlet.getResponses().put("/foo.tgz", bytes);
try {
igInstaller.install(new PackageInstallationSpec()
.setName("blah")
.setVersion("1.0.2")
.setPackageUrl("http://localhost:" + myPort + "/foo.tgz")
);
fail();
} catch (InvalidRequestException e) {
assertEquals("", e.getMessage());
}
}
@Test
public void testInstallPackageByUrl_FailingUrl() {
try {
igInstaller.install(new PackageInstallationSpec()
.setName("blah")
.setVersion("1.0.2")
.setPackageUrl("http://localhost:" + myPort + "/foo.tgz")
);
fail();
} catch (InvalidRequestException e) {
assertEquals("", e.getMessage());
}
}
@Test
public void installFromCache2() {
igInstaller.install("basisprofil.de", "0.2.40");
byte[] bytes = loadResourceAsByteArray("/packages/basisprofil.de.tar.gz");
myFakeNpmServlet.getResponses().put("/basisprofil.de/0.2.40", bytes);
igInstaller.install(new PackageInstallationSpec().setName("basisprofil.de").setVersion("0.2.40").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL));
}
}

View File

@ -1,35 +0,0 @@
package ca.uhn.fhir.jpa.packages;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import org.hl7.fhir.utilities.cache.PackageCacheManager;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.Assert.assertTrue;
public class IgInstallerTestR4 extends BaseJpaR4Test {
@Autowired
public DaoConfig daoConfig;
@Autowired
public IgInstallerSvc igInstaller;
@Before
public void before() throws IOException {
PackageCacheManager packageCacheManager = new PackageCacheManager(true, 1);
InputStream stream;
stream = IgInstallerTestDstu3.class.getResourceAsStream("NHSD.Assets.STU3.tar.gz");
packageCacheManager.addPackageToCache("NHSD.Assets.STU3", "1.0.0", stream, "NHSD.Assets.STU3");
}
@Test(expected = ImplementationGuideInstallationException.class)
public void negativeTestInstallFromCacheVersionMismatch() {
daoConfig.setAllowExternalReferences(true);
igInstaller.install("NHSD.Assets.STU3", "1.2.0");
}
}

View File

@ -0,0 +1,48 @@
package ca.uhn.fhir.jpa.packages;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.utilities.cache.IPackageCacheManager;
import org.hl7.fhir.utilities.cache.NpmPackage;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.Assert.*;
public class JpaPackageCacheTest extends BaseJpaR4Test {
@Autowired
private IPackageCacheManager myPackageCacheManager;
@Test
public void testSavePackage() throws IOException {
try (InputStream stream = IgInstallerTestDstu3.class.getResourceAsStream("/packages/basisprofil.de.tar.gz")) {
myPackageCacheManager.addPackageToCache("basisprofil.de", "0.2.40", stream, "basisprofil.de");
}
NpmPackage pkg;
pkg = myPackageCacheManager.loadPackage("basisprofil.de", null);
assertEquals("0.2.40", pkg.version());
pkg = myPackageCacheManager.loadPackage("basisprofil.de", "0.2.40");
assertEquals("0.2.40", pkg.version());
try {
myPackageCacheManager.loadPackage("basisprofil.de", "99");
fail();
} catch (ResourceNotFoundException e) {
assertEquals("Unable to locate package basisprofil.de#99", e.getMessage());
}
}
@Test
public void testSavePackageCorrectFhirVersion() {
}
}

View File

@ -0,0 +1,227 @@
package ca.uhn.fhir.jpa.packages;
import ca.uhn.fhir.jpa.dao.data.INpmPackageDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionResourceDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.test.utilities.ProxyUtil;
import ca.uhn.fhir.util.JsonUtil;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class NpmSearchTestR4 extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(NpmSearchTestR4.class);
@Autowired
public IPackageInstallerSvc igInstaller;
@Autowired
private IHapiPackageCacheManager myPackageCacheManager;
@Autowired
private NpmJpaValidationSupport myNpmJpaValidationSupport;
@Autowired
private INpmPackageDao myPackageDao;
@Autowired
private INpmPackageVersionDao myPackageVersionDao;
@Autowired
private INpmPackageVersionResourceDao myPackageVersionResourceDao;
@Before
public void before() throws Exception {
JpaPackageCache jpaPackageCache = ProxyUtil.getSingletonTarget(myPackageCacheManager, JpaPackageCache.class);
jpaPackageCache.getPackageServers().clear();
}
@Test
public void testSearch() throws IOException {
PackageInstallationSpec spec;
byte[] bytes;
bytes = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz");
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(bytes);
igInstaller.install(spec);
bytes = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz");
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(bytes);
igInstaller.install(spec);
bytes = loadClasspathBytes("/packages/nictiz.fhir.nl.stu3.questionnaires-1.0.2.tgz");
spec = new PackageInstallationSpec().setName("nictiz.fhir.nl.stu3.questionnaires").setVersion("1.0.2").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(bytes);
igInstaller.install(spec);
NpmPackageSearchResultJson search = myPackageCacheManager.search(new PackageSearchSpec());
ourLog.info("Search rersults:\r{}", JsonUtil.serialize(search));
assertEquals(2, search.getTotal());
assertEquals(2, search.getObjects().size());
assertEquals("hl7.fhir.uv.shorthand", search.getObjects().get(0).getPackage().getName());
assertEquals("Describes FHIR Shorthand (FSH), a domain-specific language (DSL) for defining the content of FHIR Implementation Guides (IG). (built Wed, Apr 1, 2020 17:24+0000+00:00)", search.getObjects().get(0).getPackage().getDescription());
assertEquals("0.12.0", search.getObjects().get(0).getPackage().getVersion());
assertEquals(3115, search.getObjects().get(0).getPackage().getBytes());
assertThat(search.getObjects().get(0).getPackage().getFhirVersion().toString(), search.getObjects().get(0).getPackage().getFhirVersion(), Matchers.contains("4.0.1"));
assertEquals("nictiz.fhir.nl.stu3.questionnaires", search.getObjects().get(1).getPackage().getName());
assertEquals("Nictiz NL package of FHIR STU3 conformance resources for MedMij information standard Questionnaires. Includes dependency on Zib2017 and SDC.\\n\\nHCIMs: https://zibs.nl/wiki/HCIM_Release_2017(EN)", search.getObjects().get(1).getPackage().getDescription());
assertEquals("1.0.2", search.getObjects().get(1).getPackage().getVersion());
assertThat(search.getObjects().get(1).getPackage().getFhirVersion().toString(), search.getObjects().get(0).getPackage().getFhirVersion(), Matchers.contains("4.0.1"));
}
@Test
public void testSearchByResourceUrl() throws IOException {
PackageInstallationSpec spec;
byte[] bytes;
bytes = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz");
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(bytes);
igInstaller.install(spec);
bytes = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz");
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(bytes);
igInstaller.install(spec);
PackageSearchSpec searchSpec;
NpmPackageSearchResultJson search;
// Matching URL
myCaptureQueriesListener.clear();
searchSpec = new PackageSearchSpec();
searchSpec.setResourceUrl("http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand");
search = myPackageCacheManager.search(searchSpec);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
ourLog.info("Search rersults:\r{}", JsonUtil.serialize(search));
assertEquals(1, search.getTotal());
assertEquals(1, search.getObjects().size());
assertEquals("hl7.fhir.uv.shorthand", search.getObjects().get(0).getPackage().getName());
assertEquals("0.12.0", search.getObjects().get(0).getPackage().getVersion());
assertEquals("Describes FHIR Shorthand (FSH), a domain-specific language (DSL) for defining the content of FHIR Implementation Guides (IG). (built Wed, Apr 1, 2020 17:24+0000+00:00)", search.getObjects().get(0).getPackage().getDescription());
assertThat(search.getObjects().get(0).getPackage().getFhirVersion(), Matchers.contains("4.0.1"));
// Non Matching URL
searchSpec = new PackageSearchSpec();
searchSpec.setResourceUrl("http://foo");
search = myPackageCacheManager.search(searchSpec);
ourLog.info("Search rersults:\r{}", JsonUtil.serialize(search));
assertEquals(0, search.getTotal());
assertEquals(0, search.getObjects().size());
}
@Test
public void testSearchByFhirVersion() throws IOException {
PackageInstallationSpec spec;
byte[] bytes;
bytes = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz");
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(bytes);
igInstaller.install(spec);
PackageSearchSpec searchSpec;
NpmPackageSearchResultJson search;
// Matching by name
myCaptureQueriesListener.clear();
searchSpec = new PackageSearchSpec();
searchSpec.setFhirVersion("R4");
search = myPackageCacheManager.search(searchSpec);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
ourLog.info("Search rersults:\r{}", JsonUtil.serialize(search));
assertEquals(1, search.getTotal());
assertEquals("hl7.fhir.uv.shorthand", search.getObjects().get(0).getPackage().getName());
assertEquals("4.0.1", search.getObjects().get(0).getPackage().getFhirVersion().get(0));
// Matching FHIR version
myCaptureQueriesListener.clear();
searchSpec = new PackageSearchSpec();
searchSpec.setFhirVersion("4.0.1");
search = myPackageCacheManager.search(searchSpec);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
ourLog.info("Search rersults:\r{}", JsonUtil.serialize(search));
assertEquals(1, search.getTotal());
assertEquals("hl7.fhir.uv.shorthand", search.getObjects().get(0).getPackage().getName());
// Partial Matching FHIR version
myCaptureQueriesListener.clear();
searchSpec = new PackageSearchSpec();
searchSpec.setFhirVersion("4.0");
search = myPackageCacheManager.search(searchSpec);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
ourLog.info("Search rersults:\r{}", JsonUtil.serialize(search));
assertEquals(1, search.getTotal());
assertEquals("hl7.fhir.uv.shorthand", search.getObjects().get(0).getPackage().getName());
// Non Matching URL
searchSpec = new PackageSearchSpec();
searchSpec.setResourceUrl("http://foo");
search = myPackageCacheManager.search(searchSpec);
ourLog.info("Search rersults:\r{}", JsonUtil.serialize(search));
assertEquals(0, search.getTotal());
assertEquals(0, search.getObjects().size());
}
@Test
public void testSearchByDescription() throws IOException {
PackageInstallationSpec spec;
byte[] bytes;
bytes = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz");
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(bytes);
igInstaller.install(spec);
bytes = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz");
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(bytes);
igInstaller.install(spec);
PackageSearchSpec searchSpec;
NpmPackageSearchResultJson search;
// Matching URL
myCaptureQueriesListener.clear();
searchSpec = new PackageSearchSpec();
searchSpec.setDescription("shorthand");
search = myPackageCacheManager.search(searchSpec);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
runInTransaction(()->{
ourLog.info("Versions:\n * {}", myPackageVersionDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
ourLog.info("Search rersults:\r{}", JsonUtil.serialize(search));
assertEquals(1, search.getTotal());
assertEquals(1, search.getObjects().size());
assertEquals("hl7.fhir.uv.shorthand", search.getObjects().get(0).getPackage().getName());
assertEquals("0.12.0", search.getObjects().get(0).getPackage().getVersion());
assertEquals("Describes FHIR Shorthand (FSH), a domain-specific language (DSL) for defining the content of FHIR Implementation Guides (IG). (built Wed, Apr 1, 2020 17:24+0000+00:00)", search.getObjects().get(0).getPackage().getDescription());
assertThat(search.getObjects().get(0).getPackage().getFhirVersion(), Matchers.contains("4.0.1"));
// Non Matching URL
searchSpec = new PackageSearchSpec();
searchSpec.setResourceUrl("http://foo");
search = myPackageCacheManager.search(searchSpec);
ourLog.info("Search rersults:\r{}", JsonUtil.serialize(search));
assertEquals(0, search.getTotal());
assertEquals(0, search.getObjects().size());
}
}

View File

@ -0,0 +1,128 @@
package ca.uhn.fhir.jpa.packages;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.ProxyUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class NpmTestDstu3 extends BaseJpaDstu3Test {
private static final Logger ourLog = LoggerFactory.getLogger(FakeNpmServlet.class);
@Autowired
public PackageInstallerSvcImpl igInstaller;
@Autowired
private IHapiPackageCacheManager myPackageCacheManager;
@Autowired
private NpmJpaValidationSupport myNpmJpaValidationSupport;
private Server myServer;
private final Map<String, byte[]> myResponses = new HashMap<>();
@Before
public void before() throws Exception {
JpaPackageCache jpaPackageCache = ProxyUtil.getSingletonTarget(myPackageCacheManager, JpaPackageCache.class);
myServer = new Server(0);
ServletHandler proxyHandler = new ServletHandler();
FakeNpmServlet fakeNpmServlet = new FakeNpmServlet();
ServletHolder servletHolder = new ServletHolder(fakeNpmServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
myServer.setHandler(proxyHandler);
myServer.start();
int port = JettyUtil.getPortForStartedServer(myServer);
jpaPackageCache.getPackageServers().clear();
jpaPackageCache.addPackageServer("http://localhost:" + port);
myResponses.clear();
}
@After
public void after() throws Exception {
JettyUtil.closeServer(myServer);
}
@Test
public void installDstu3Package() throws Exception {
byte[] bytes = loadClasspathBytes("/packages/basisprofil.de.tar.gz");
myResponses.put("/basisprofil.de/0.2.40", bytes);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("basisprofil.de").setVersion("0.2.40").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
// Be sure no further communication with the server
JettyUtil.closeServer(myServer);
StructureDefinition sd = (StructureDefinition) myNpmJpaValidationSupport.fetchStructureDefinition("http://fhir.de/StructureDefinition/condition-de-basis/0.2");
assertEquals("http://fhir.de/StructureDefinition/condition-de-basis/0.2", sd.getUrl());
ValueSet vs = (ValueSet) myNpmJpaValidationSupport.fetchValueSet("http://fhir.de/ValueSet/ifa/pzn");
assertEquals("http://fhir.de/ValueSet/ifa/pzn", vs.getUrl());
CodeSystem cs = (CodeSystem) myNpmJpaValidationSupport.fetchCodeSystem("http://fhir.de/CodeSystem/deuev/anlage-8-laenderkennzeichen");
assertEquals("http://fhir.de/CodeSystem/deuev/anlage-8-laenderkennzeichen", cs.getUrl());
// Try and validate using a profile from the IG
Condition condition = new Condition();
condition.setClinicalStatus(Condition.ConditionClinicalStatus.RESOLVED);
condition.getMeta().addProfile("http://fhir.de/StructureDefinition/condition-de-basis/0.2");
try {
myConditionDao.validate(condition, null, null, null, ValidationModeEnum.CREATE, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
ourLog.info("Fail Outcome: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
assertEquals("Profile http://fhir.de/StructureDefinition/condition-de-basis/0.2, Element 'Condition.subject': minimum required = 1, but only found 0", oo.getIssueFirstRep().getDiagnostics());
}
}
private class FakeNpmServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String requestUrl = req.getRequestURI();
if (myResponses.containsKey(requestUrl)) {
ourLog.info("Responding to request: {}", requestUrl);
resp.setStatus(200);
resp.setHeader(Constants.HEADER_CONTENT_TYPE, "application/gzip");
resp.getOutputStream().write(myResponses.get(requestUrl));
resp.getOutputStream().close();
} else {
ourLog.warn("Unknown request: {}", requestUrl);
resp.sendError(404);
}
}
}
}

View File

@ -0,0 +1,474 @@
package ca.uhn.fhir.jpa.packages;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.INpmPackageDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionResourceDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.model.entity.NpmPackageEntity;
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity;
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionResourceEntity;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.ProxyUtil;
import ca.uhn.fhir.util.JsonUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.ImplementationGuide;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.utilities.cache.NpmPackage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
public class NpmTestR4 extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(NpmTestR4.class);
@Autowired
public IPackageInstallerSvc igInstaller;
@Autowired
private IHapiPackageCacheManager myPackageCacheManager;
@Autowired
private NpmJpaValidationSupport myNpmJpaValidationSupport;
private Server myServer;
@Autowired
private INpmPackageDao myPackageDao;
@Autowired
private INpmPackageVersionDao myPackageVersionDao;
@Autowired
private INpmPackageVersionResourceDao myPackageVersionResourceDao;
private FakeNpmServlet myFakeNpmServlet;
@Before
public void before() throws Exception {
JpaPackageCache jpaPackageCache = ProxyUtil.getSingletonTarget(myPackageCacheManager, JpaPackageCache.class);
myServer = new Server(0);
ServletHandler proxyHandler = new ServletHandler();
myFakeNpmServlet = new FakeNpmServlet();
ServletHolder servletHolder = new ServletHolder(myFakeNpmServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
myServer.setHandler(proxyHandler);
myServer.start();
int port = JettyUtil.getPortForStartedServer(myServer);
jpaPackageCache.getPackageServers().clear();
jpaPackageCache.addPackageServer("http://localhost:" + port);
myFakeNpmServlet.myResponses.clear();
}
@After
public void after() throws Exception {
JettyUtil.closeServer(myServer);
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
}
@Test
public void testCacheDstu3Package() throws Exception {
byte[] bytes = loadClasspathBytes("/packages/nictiz.fhir.nl.stu3.questionnaires-1.0.2.tgz");
myFakeNpmServlet.myResponses.put("/nictiz.fhir.nl.stu3.questionnaires/1.0.2", bytes);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("nictiz.fhir.nl.stu3.questionnaires").setVersion("1.0.2").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
// Be sure no further communication with the server
JettyUtil.closeServer(myServer);
// Make sure we can fetch the package by ID and Version
NpmPackage pkg = myPackageCacheManager.loadPackage("nictiz.fhir.nl.stu3.questionnaires", "1.0.2");
assertEquals("Nictiz NL package of FHIR STU3 conformance resources for MedMij information standard Questionnaires. Includes dependency on Zib2017 and SDC.\\n\\nHCIMs: https://zibs.nl/wiki/HCIM_Release_2017(EN)", pkg.description());
// Make sure we can fetch the package by ID
pkg = myPackageCacheManager.loadPackage("nictiz.fhir.nl.stu3.questionnaires", null);
assertEquals("1.0.2", pkg.version());
assertEquals("Nictiz NL package of FHIR STU3 conformance resources for MedMij information standard Questionnaires. Includes dependency on Zib2017 and SDC.\\n\\nHCIMs: https://zibs.nl/wiki/HCIM_Release_2017(EN)", pkg.description());
// Fetch resource by URL
FhirContext fhirContext = FhirContext.forDstu3();
runInTransaction(() -> {
IBaseResource asset = myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.DSTU3, "http://nictiz.nl/fhir/StructureDefinition/vl-QuestionnaireResponse");
assertThat(fhirContext.newJsonParser().encodeResourceToString(asset), containsString("\"url\":\"http://nictiz.nl/fhir/StructureDefinition/vl-QuestionnaireResponse\",\"version\":\"1.0.1\""));
});
// Fetch resource by URL with version
runInTransaction(() -> {
IBaseResource asset = myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.DSTU3, "http://nictiz.nl/fhir/StructureDefinition/vl-QuestionnaireResponse|1.0.1");
assertThat(fhirContext.newJsonParser().encodeResourceToString(asset), containsString("\"url\":\"http://nictiz.nl/fhir/StructureDefinition/vl-QuestionnaireResponse\",\"version\":\"1.0.1\""));
});
// This was saved but is the wrong version of FHIR for this server
assertNull(myNpmJpaValidationSupport.fetchStructureDefinition("http://fhir.de/StructureDefinition/condition-de-basis/0.2"));
}
@Test
public void testInstallR4Package() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
byte[] bytes = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz");
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.12.0", bytes);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
igInstaller.install(spec);
// Be sure no further communication with the server
JettyUtil.closeServer(myServer);
// Make sure we can fetch the package by ID and Version
NpmPackage pkg = myPackageCacheManager.loadPackage("hl7.fhir.uv.shorthand", "0.12.0");
assertEquals("Describes FHIR Shorthand (FSH), a domain-specific language (DSL) for defining the content of FHIR Implementation Guides (IG). (built Wed, Apr 1, 2020 17:24+0000+00:00)", pkg.description());
// Make sure we can fetch the package by ID
pkg = myPackageCacheManager.loadPackage("hl7.fhir.uv.shorthand", null);
assertEquals("0.12.0", pkg.version());
assertEquals("Describes FHIR Shorthand (FSH), a domain-specific language (DSL) for defining the content of FHIR Implementation Guides (IG). (built Wed, Apr 1, 2020 17:24+0000+00:00)", pkg.description());
// Make sure DB rows were saved
runInTransaction(() -> {
NpmPackageEntity pkgEntity = myPackageDao.findByPackageId("hl7.fhir.uv.shorthand").orElseThrow(() -> new IllegalArgumentException());
assertEquals("hl7.fhir.uv.shorthand", pkgEntity.getPackageId());
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.12.0").orElseThrow(() -> new IllegalArgumentException());
assertEquals("hl7.fhir.uv.shorthand", versionEntity.getPackageId());
assertEquals("0.12.0", versionEntity.getVersionId());
assertEquals(3001, versionEntity.getPackageSizeBytes());
assertEquals(true, versionEntity.isCurrentVersion());
assertEquals("hl7.fhir.uv.shorthand", versionEntity.getPackageId());
assertEquals("4.0.1", versionEntity.getFhirVersionId());
assertEquals(FhirVersionEnum.R4, versionEntity.getFhirVersion());
NpmPackageVersionResourceEntity resource = myPackageVersionResourceDao.findCurrentVersionByCanonicalUrl(Pageable.unpaged(), FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand").getContent().get(0);
assertEquals("http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand", resource.getCanonicalUrl());
assertEquals("0.12.0", resource.getCanonicalVersion());
assertEquals("ImplementationGuide-hl7.fhir.uv.shorthand.json", resource.getFilename());
assertEquals("4.0.1", resource.getFhirVersionId());
assertEquals(FhirVersionEnum.R4, resource.getFhirVersion());
assertEquals(6155, resource.getResSizeBytes());
});
// Fetch resource by URL
runInTransaction(() -> {
IBaseResource asset = myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand");
assertThat(myFhirCtx.newJsonParser().encodeResourceToString(asset), containsString("\"url\":\"http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand\",\"version\":\"0.12.0\""));
});
// Fetch resource by URL with version
runInTransaction(() -> {
IBaseResource asset = myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand|0.12.0");
assertThat(myFhirCtx.newJsonParser().encodeResourceToString(asset), containsString("\"url\":\"http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand\",\"version\":\"0.12.0\""));
});
// Search for the installed resource
runInTransaction(() -> {
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(StructureDefinition.SP_URL, new UriParam("http://hl7.org/fhir/uv/shorthand/CodeSystem/shorthand-code-system"));
IBundleProvider outcome = myCodeSystemDao.search(map);
assertEquals(1, outcome.sizeOrThrowNpe());
});
}
@Test
public void testLoadPackageMetadata() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.12.0", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz"));
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz"));
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
runInTransaction(() -> {
NpmPackageMetadataJson metadata = myPackageCacheManager.loadPackageMetadata("hl7.fhir.uv.shorthand");
try {
ourLog.info(JsonUtil.serialize(metadata));
assertEquals("0.12.0", metadata.getDistTags().getLatest());
assertThat(metadata.getVersions().keySet(), contains("0.12.0", "0.11.1"));
NpmPackageMetadataJson.Version version0120 = metadata.getVersions().get("0.12.0");
assertEquals(3001, version0120.getBytes());
} catch (IOException e) {
throw new InternalErrorException(e);
}
});
}
@Test
public void testLoadPackageUsingImpreciseId() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.12.0", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz"));
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz"));
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.0", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.0.tgz"));
PackageInstallationSpec spec;
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
ourLog.info("Install messages:\n * {}", outcome.getMessage().stream().collect(Collectors.joining("\n * ")));
assertThat(outcome.getMessage(), hasItem("Marking package hl7.fhir.uv.shorthand#0.12.0 as current version"));
assertThat(outcome.getMessage(), hasItem("Indexing Resource[package/CodeSystem-shorthand-code-system.json] with URL: http://hl7.org/fhir/uv/shorthand/CodeSystem/shorthand-code-system|0.12.0"));
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
outcome = igInstaller.install(spec);
ourLog.info("Install messages:\n * {}", outcome.getMessage().stream().collect(Collectors.joining("\n * ")));
assertThat(outcome.getMessage(), not(hasItem("Marking package hl7.fhir.uv.shorthand#0.11.1 as current version")));
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
NpmPackage pkg;
pkg = myPackageCacheManager.loadPackage("hl7.fhir.uv.shorthand", "0.11.x");
assertEquals("0.11.1", pkg.version());
pkg = myPackageCacheManager.loadPackage("hl7.fhir.uv.shorthand", "0.12.x");
assertEquals("0.12.0", pkg.version());
}
@Test
public void testInstallNewerPackageUpdatesLatestVersionFlag() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
byte[] contents0111 = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz");
byte[] contents0120 = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz");
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", contents0111);
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.12.0", contents0120);
// Install older version
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
// Older version is current
runInTransaction(() -> {
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.11.1").orElseThrow(() -> new IllegalArgumentException());
assertEquals(true, versionEntity.isCurrentVersion());
});
// Fetching a resource should return the older version
runInTransaction(() -> {
ImplementationGuide ig = (ImplementationGuide) myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand");
assertEquals("0.11.1", ig.getVersion());
});
// Now install newer version
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
// Newer version is current
runInTransaction(() -> {
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.11.1").orElseThrow(() -> new IllegalArgumentException());
assertEquals(false, versionEntity.isCurrentVersion());
versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.12.0").orElseThrow(() -> new IllegalArgumentException());
assertEquals(true, versionEntity.isCurrentVersion());
});
// Fetching a resource should return the newer version
runInTransaction(() -> {
ImplementationGuide ig = (ImplementationGuide) myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand");
assertEquals("0.12.0", ig.getVersion());
});
}
@Test
public void testInstallOlderPackageDoesntUpdateLatestVersionFlag() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.12.0", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz"));
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz"));
// Install newer version
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
runInTransaction(() -> {
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.12.0").orElseThrow(() -> new IllegalArgumentException());
assertEquals(true, versionEntity.isCurrentVersion());
});
// Fetching a resource should return the older version
runInTransaction(() -> {
ImplementationGuide ig = (ImplementationGuide) myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand");
assertEquals("0.12.0", ig.getVersion());
});
// Install older version
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
// Newer version is still current
runInTransaction(() -> {
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.11.1").orElseThrow(() -> new IllegalArgumentException());
assertEquals(false, versionEntity.isCurrentVersion());
versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.12.0").orElseThrow(() -> new IllegalArgumentException());
assertEquals(true, versionEntity.isCurrentVersion());
});
// Fetching a resource should return the newer version
runInTransaction(() -> {
ImplementationGuide ig = (ImplementationGuide) myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand");
assertEquals("0.12.0", ig.getVersion());
});
}
@Test
public void testInstallAlreadyExistingIsIgnored() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.12.0", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz"));
// Install
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
runInTransaction(() -> {
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.12.0").orElseThrow(() -> new IllegalArgumentException());
assertEquals(true, versionEntity.isCurrentVersion());
});
// Install same again
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
igInstaller.install(spec);
runInTransaction(() -> {
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.12.0").orElseThrow(() -> new IllegalArgumentException());
assertEquals(true, versionEntity.isCurrentVersion());
});
}
@Test
public void testLoadContents() throws IOException {
byte[] contents0111 = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz");
byte[] contents0120 = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz");
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(contents0111);
igInstaller.install(spec);
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY).setPackageContents(contents0120);
igInstaller.install(spec);
assertArrayEquals(contents0111, myPackageCacheManager.loadPackageContents("hl7.fhir.uv.shorthand", "0.11.1").getBytes());
assertArrayEquals(contents0120, myPackageCacheManager.loadPackageContents("hl7.fhir.uv.shorthand", "0.12.0").getBytes());
assertArrayEquals(contents0120, myPackageCacheManager.loadPackageContents("hl7.fhir.uv.shorthand", "latest").getBytes());
assertEquals("0.11.1", myPackageCacheManager.loadPackageContents("hl7.fhir.uv.shorthand", "0.11.1").getVersion());
assertEquals("0.12.0", myPackageCacheManager.loadPackageContents("hl7.fhir.uv.shorthand", "0.12.0").getVersion());
assertEquals("0.12.0", myPackageCacheManager.loadPackageContents("hl7.fhir.uv.shorthand", "latest").getVersion());
assertEquals(null, myPackageCacheManager.loadPackageContents("hl7.fhir.uv.shorthand", "1.2.3"));
assertEquals(null, myPackageCacheManager.loadPackageContents("foo", "1.2.3"));
}
@Test
public void testDeletePackage() throws IOException {
myDaoConfig.setAllowExternalReferences(true);
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.12.0", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz"));
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.1.tgz"));
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.0", loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.11.0.tgz"));
igInstaller.install(new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
igInstaller.install(new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
igInstaller.install(new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
runInTransaction(() -> {
Slice<NpmPackageVersionResourceEntity> versions = myPackageVersionResourceDao.findCurrentVersionByCanonicalUrl(Pageable.unpaged(), FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ValueSet/shorthand-instance-tags");
assertEquals(1, versions.getNumberOfElements());
NpmPackageVersionResourceEntity resource = versions.getContent().get(0);
assertEquals("0.12.0", resource.getCanonicalVersion());
});
myPackageCacheManager.uninstallPackage("hl7.fhir.uv.shorthand", "0.12.0");
runInTransaction(() -> {
Slice<NpmPackageVersionResourceEntity> versions = myPackageVersionResourceDao.findCurrentVersionByCanonicalUrl(Pageable.unpaged(), FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ValueSet/shorthand-instance-tags");
assertEquals(1, versions.getNumberOfElements());
NpmPackageVersionResourceEntity resource = versions.getContent().get(0);
assertEquals("0.11.1", resource.getCanonicalVersion());
});
myPackageCacheManager.uninstallPackage("hl7.fhir.uv.shorthand", "0.11.0");
runInTransaction(() -> {
Slice<NpmPackageVersionResourceEntity> versions = myPackageVersionResourceDao.findCurrentVersionByCanonicalUrl(Pageable.unpaged(), FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ValueSet/shorthand-instance-tags");
assertEquals(1, versions.getNumberOfElements());
NpmPackageVersionResourceEntity resource = versions.getContent().get(0);
assertEquals("0.11.1", resource.getCanonicalVersion());
});
myPackageCacheManager.uninstallPackage("hl7.fhir.uv.shorthand", "0.11.1");
runInTransaction(() -> {
Slice<NpmPackageVersionResourceEntity> versions = myPackageVersionResourceDao.findCurrentVersionByCanonicalUrl(Pageable.unpaged(), FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ValueSet/shorthand-instance-tags");
assertEquals(0, versions.getNumberOfElements());
});
}
static class FakeNpmServlet extends HttpServlet {
private final Map<String, byte[]> myResponses = new HashMap<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String requestUrl = req.getRequestURI();
if (myResponses.containsKey(requestUrl)) {
ourLog.info("Responding to request: {}", requestUrl);
resp.setStatus(200);
resp.setHeader(Constants.HEADER_CONTENT_TYPE, "application/gzip");
resp.getOutputStream().write(myResponses.get(requestUrl));
resp.getOutputStream().close();
} else {
ourLog.warn("Unknown request: {}", requestUrl);
resp.sendError(404);
}
}
public Map<String, byte[]> getResponses() {
return myResponses;
}
}
}

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.jpa.packages;
import ca.uhn.fhir.util.JsonUtil;
import org.junit.Test;
import java.io.IOException;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
public class PackageInstallationSpecTest {
@Test
public void testExampleSupplier() throws IOException {
PackageInstallationSpec output = new PackageInstallationSpec.ExampleSupplier().get();
String json = JsonUtil.serialize(output);
assertThat(json, containsString("\"name\" : \"hl7.fhir.us.core\""));
output = new PackageInstallationSpec.ExampleSupplier2().get();
json = JsonUtil.serialize(output);
assertThat(json, containsString("\"packageUrl\" : \"classpath:/my-resources.tgz\""));
}
}

View File

@ -0,0 +1,40 @@
package ca.uhn.fhir.jpa.packages;
import org.junit.Test;
import static com.google.common.collect.Lists.newArrayList;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.thymeleaf.util.ListUtils.sort;
public class PackageVersionComparatorTest {
private PackageVersionComparator myCmp = new PackageVersionComparator();
@Test
public void testCompareVersion() {
assertThat(sort(newArrayList("10.1", "10.2"), myCmp), contains("10.1", "10.2"));
assertThat(sort(newArrayList("10.2", "10.1"), myCmp), contains("10.1", "10.2"));
assertThat(sort(newArrayList("10.1.2.3", "9.1.2.3"), myCmp), contains("9.1.2.3", "10.1.2.3"));
assertThat(sort(newArrayList("9.1.2.3", "10.1.2.3"), myCmp), contains("9.1.2.3", "10.1.2.3"));
assertThat(sort(newArrayList("9.1.2.3", "9.1"), myCmp), contains("9.1", "9.1.2.3"));
assertThat(sort(newArrayList("9.1", "9.1.2.3"), myCmp), contains("9.1", "9.1.2.3"));
assertThat(sort(newArrayList("A", "1"), myCmp), contains("1", "A"));
assertThat(sort(newArrayList("1", "A"), myCmp), contains("1", "A"));
assertThat(sort(newArrayList("A", "B"), myCmp), contains("A", "B"));
}
@Test
public void testIsEquivalent() {
assertTrue(PackageVersionComparator.isEquivalent("1.2.x", "1.2.3"));
assertTrue(PackageVersionComparator.isEquivalent("1.2", "1.2.3"));
assertTrue(PackageVersionComparator.isEquivalent("1.2.3", "1.2.3"));
assertFalse(PackageVersionComparator.isEquivalent("1.2.4", "1.2.3"));
assertFalse(PackageVersionComparator.isEquivalent("1.3", "1.2.3"));
}
}

View File

@ -68,7 +68,60 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
init420(); // 20191015 - 20200217
init430(); // Replaced by 5.0.0
init500(); // 20200218 - 20200513
init501(); // 20200514 - present
init501(); // 20200514 - 20200515
init510(); // 20200516 - present
}
private void init510() {
Builder version = forVersion(VersionEnum.V5_1_0);
// NPM Packages
version.addIdGenerator("20200610.1", "SEQ_NPM_PACK");
Builder.BuilderAddTableByColumns pkg = version.addTableByColumns("20200610.2", "NPM_PACKAGE", "PID");
pkg.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
pkg.addColumn("PACKAGE_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkg.addColumn("CUR_VERSION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkg.addColumn("UPDATED_TIME").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP);
pkg.addColumn("PACKAGE_DESC").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkg.addIndex("20200610.3", "IDX_PACK_ID").unique(true).withColumns("PACKAGE_ID");
version.addIdGenerator("20200610.4", "SEQ_NPM_PACKVER");
Builder.BuilderAddTableByColumns pkgVer = version.addTableByColumns("20200610.5", "NPM_PACKAGE_VER", "PID");
pkgVer.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
pkgVer.addColumn("PACKAGE_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkgVer.addColumn("VERSION_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkgVer.addColumn("PACKAGE_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
pkgVer.addColumn("BINARY_RES_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
pkgVer.addColumn("SAVED_TIME").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP);
pkgVer.addColumn("PKG_DESC").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkgVer.addColumn("DESC_UPPER").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkgVer.addColumn("CURRENT_VERSION").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
pkgVer.addColumn("FHIR_VERSION_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 10);
pkgVer.addColumn("FHIR_VERSION").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 10);
pkgVer.addColumn("PACKAGE_SIZE_BYTES").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
pkgVer.addColumn("UPDATED_TIME").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP);
pkgVer.addForeignKey("20200610.6", "FK_NPM_PKV_PKG").toColumn("PACKAGE_PID").references("NPM_PACKAGE", "PID");
pkgVer.addForeignKey("20200610.7", "FK_NPM_PKV_RESID").toColumn("BINARY_RES_ID").references("HFJ_RESOURCE", "RES_ID");
pkgVer.addIndex("20200610.8", "IDX_PACKVER").unique(true).withColumns("PACKAGE_ID", "VERSION_ID");
version.addIdGenerator("20200610.9", "SEQ_NPM_PACKVERRES");
Builder.BuilderAddTableByColumns pkgVerRes = version.addTableByColumns("20200610.10", "NPM_PACKAGE_VER_RES", "PID");
pkgVerRes.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
pkgVerRes.addColumn("PACKVER_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
pkgVerRes.addColumn("BINARY_RES_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
pkgVerRes.addColumn("FILE_DIR").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkgVerRes.addColumn("FILE_NAME").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkgVerRes.addColumn("RES_TYPE").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 40);
pkgVerRes.addColumn("CANONICAL_URL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkgVerRes.addColumn("CANONICAL_VERSION").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
pkgVerRes.addColumn("FHIR_VERSION_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 10);
pkgVerRes.addColumn("FHIR_VERSION").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 10);
pkgVerRes.addColumn("RES_SIZE_BYTES").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
pkgVerRes.addColumn("UPDATED_TIME").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP);
pkgVerRes.addForeignKey("20200610.11", "FK_NPM_PACKVERRES_PACKVER").toColumn("PACKVER_PID").references("NPM_PACKAGE_VER", "PID");
pkgVerRes.addForeignKey("20200610.12", "FK_NPM_PKVR_RESID").toColumn("BINARY_RES_ID").references("HFJ_RESOURCE", "PID");
pkgVerRes.addIndex("20200610.13", "IDX_PACKVERRES_URL").unique(false).withColumns("CANONICAL_URL");
}
private void init501() { //20200514 - present
@ -246,7 +299,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
protected void init420() { // 20191015 - 20200217
Builder version = forVersion(VersionEnum.V4_2_0);
// TermValueSetConceptDesignation
// TermValueSetConceptDesignation
version.onTable("TRM_VALUESET_C_DESIGNATION").dropIndex("20200202.1", "IDX_VALUESET_C_DSGNTN_VAL").failureAllowed();
Builder.BuilderWithTableName searchTable = version.onTable("HFJ_SEARCH");
searchTable.dropIndex("20200203.1", "IDX_SEARCH_LASTRETURNED");
@ -446,7 +499,6 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.renameColumn("20190722.29", "CODE", "CODEVAL", false, true);
// TermValueSet
version.startSectionWithMessage("Processing table: TRM_VALUESET");
version.addIdGenerator("20190722.30", "SEQ_VALUESET_PID");
@ -749,9 +801,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.nullable()
.type(AddColumnTask.ColumnTypeEnum.LONG);
spidxString
.addIndex("20180903.27","IDX_SP_STRING_HASH_EXCT")
.addIndex("20180903.27", "IDX_SP_STRING_HASH_EXCT")
.unique(false)
.withColumns( "HASH_EXACT");
.withColumns("HASH_EXACT");
spidxString
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.28")
.setColumnName("HASH_NORM_PREFIX")
@ -789,17 +841,17 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.unique(false)
.withColumns("HASH_IDENTITY");
spidxToken
.addIndex("20180903.36","IDX_SP_TOKEN_HASH_S")
.addIndex("20180903.36", "IDX_SP_TOKEN_HASH_S")
.unique(false)
.withColumns( "HASH_SYS");
.withColumns("HASH_SYS");
spidxToken
.addIndex("20180903.37", "IDX_SP_TOKEN_HASH_SV")
.unique(false)
.withColumns("HASH_SYS_AND_VALUE");
spidxToken
.addIndex("20180903.38","IDX_SP_TOKEN_HASH_V")
.addIndex("20180903.38", "IDX_SP_TOKEN_HASH_V")
.unique(false)
.withColumns( "HASH_VALUE");
.withColumns("HASH_VALUE");
spidxToken
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.39")
.setColumnName("HASH_IDENTITY")
@ -827,9 +879,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.nullable()
.type(AddColumnTask.ColumnTypeEnum.LONG);
spidxUri
.addIndex("20180903.43","IDX_SP_URI_HASH_URI")
.addIndex("20180903.43", "IDX_SP_URI_HASH_URI")
.unique(false)
.withColumns( "HASH_URI");
.withColumns("HASH_URI");
spidxUri
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.44")
.setColumnName("HASH_IDENTITY")
@ -885,7 +937,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
trmConcept
.addIndex("20180903.51", "IDX_CONCEPT_UPDATED")
.unique(false)
.withColumns( "CONCEPT_UPDATED");
.withColumns("CONCEPT_UPDATED");
trmConcept
.modifyColumn("20180903.52", "CODE")
.nonNullable()

View File

@ -0,0 +1,114 @@
package ca.uhn.fhir.jpa.model.entity;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import java.util.Date;
import java.util.List;
@Entity()
@Table(name = "NPM_PACKAGE", uniqueConstraints = {
@UniqueConstraint(name = "IDX_PACK_ID", columnNames = "PACKAGE_ID")
})
public class NpmPackageEntity {
protected static final int PACKAGE_ID_LENGTH = 200;
@SequenceGenerator(name = "SEQ_NPM_PACK", sequenceName = "SEQ_NPM_PACK")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_NPM_PACK")
@Id
@Column(name = "PID")
private Long myId;
@Column(name = "PACKAGE_ID", length = PACKAGE_ID_LENGTH, nullable = false)
private String myPackageId;
@Column(name = "CUR_VERSION_ID", length = NpmPackageVersionEntity.VERSION_ID_LENGTH, nullable = true)
private String myCurrentVersionId;
@Temporal(TemporalType.TIMESTAMP)
@Version
@Column(name = "UPDATED_TIME", nullable = false)
private Date myVersion;
@Column(name = "PACKAGE_DESC", length = NpmPackageVersionEntity.VERSION_ID_LENGTH, nullable = true)
private String myDescription;
@OneToMany(mappedBy = "myPackage")
private List<NpmPackageVersionEntity> myVersions;
public String getDescription() {
return myDescription;
}
public void setDescription(String theDescription) {
myDescription = theDescription;
}
public String getPackageId() {
return myPackageId;
}
public void setPackageId(String thePackageId) {
myPackageId = thePackageId;
}
@Override
public boolean equals(Object theO) {
if (this == theO) {
return true;
}
if (theO == null || getClass() != theO.getClass()) {
return false;
}
NpmPackageEntity that = (NpmPackageEntity) theO;
return new EqualsBuilder()
.append(myPackageId, that.myPackageId)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myPackageId)
.toHashCode();
}
public String getCurrentVersionId() {
return myCurrentVersionId;
}
public void setCurrentVersionId(String theCurrentVersionId) {
myCurrentVersionId = theCurrentVersionId;
}
}

View File

@ -0,0 +1,192 @@
package ca.uhn.fhir.jpa.model.entity;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.util.StringUtil;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
import java.util.Date;
import java.util.List;
@Entity()
@Table(name = "NPM_PACKAGE_VER", uniqueConstraints = {
}, indexes = {
@Index(name = "IDX_PACKVER", columnList = "PACKAGE_ID,VERSION_ID", unique = true)
})
public class NpmPackageVersionEntity {
public static final int VERSION_ID_LENGTH = 200;
public static final int FHIR_VERSION_LENGTH = 10;
@SequenceGenerator(name = "SEQ_NPM_PACKVER", sequenceName = "SEQ_NPM_PACKVER")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_NPM_PACKVER")
@Id
@Column(name = "PID")
private Long myId;
@Column(name = "PACKAGE_ID", length = NpmPackageEntity.PACKAGE_ID_LENGTH, nullable = false)
private String myPackageId;
@Column(name = "VERSION_ID", length = NpmPackageVersionEntity.VERSION_ID_LENGTH, nullable = false)
private String myVersionId;
@ManyToOne
@JoinColumn(name = "PACKAGE_PID", nullable = false, foreignKey = @ForeignKey(name = "FK_NPM_PKV_PKG"))
private NpmPackageEntity myPackage;
@OneToOne
@JoinColumn(name = "BINARY_RES_ID", referencedColumnName = "RES_ID", nullable = false, foreignKey = @ForeignKey(name = "FK_NPM_PKV_RESID"))
private ResourceTable myPackageBinary;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "SAVED_TIME", nullable = false)
private Date mySavedTime;
@Column(name = "PKG_DESC", nullable = false, length = 200)
private String myDescription;
@Column(name = "DESC_UPPER", nullable = false, length = 200)
private String myDescriptionUpper;
@Column(name = "CURRENT_VERSION", nullable = false)
private boolean myCurrentVersion;
@Column(name = "FHIR_VERSION_ID", length = NpmPackageVersionEntity.FHIR_VERSION_LENGTH, nullable = false)
private String myFhirVersionId;
@Enumerated(EnumType.STRING)
@Column(name = "FHIR_VERSION", length = NpmPackageVersionEntity.FHIR_VERSION_LENGTH, nullable = false)
private FhirVersionEnum myFhirVersion;
@Column(name = "PACKAGE_SIZE_BYTES", nullable = false)
private long myPackageSizeBytes;
@Temporal(TemporalType.TIMESTAMP)
@Version
@Column(name = "UPDATED_TIME", nullable = false)
private Date myUpdatedTime;
@OneToMany(mappedBy = "myPackageVersion")
private List<NpmPackageVersionResourceEntity> myResources;
public Date getUpdatedTime() {
return myUpdatedTime;
}
public long getPackageSizeBytes() {
return myPackageSizeBytes;
}
public void setPackageSizeBytes(long thePackageSizeBytes) {
myPackageSizeBytes = thePackageSizeBytes;
}
public boolean isCurrentVersion() {
return myCurrentVersion;
}
public void setCurrentVersion(boolean theCurrentVersion) {
myCurrentVersion = theCurrentVersion;
}
public String getPackageId() {
return myPackageId;
}
public void setPackageId(String thePackageId) {
myPackageId = thePackageId;
}
public String getVersionId() {
return myVersionId;
}
public void setVersionId(String theVersionId) {
myVersionId = theVersionId;
}
public String getFhirVersionId() {
return myFhirVersionId;
}
public void setFhirVersionId(String theFhirVersionId) {
myFhirVersionId = theFhirVersionId;
}
public FhirVersionEnum getFhirVersion() {
return myFhirVersion;
}
public void setFhirVersion(FhirVersionEnum theFhirVersion) {
myFhirVersion = theFhirVersion;
}
public NpmPackageEntity getPackage() {
return myPackage;
}
public void setPackage(NpmPackageEntity thePackage) {
myPackage = thePackage;
}
public ResourceTable getPackageBinary() {
return myPackageBinary;
}
public void setPackageBinary(ResourceTable thePackageBinary) {
myPackageBinary = thePackageBinary;
}
public void setSavedTime(Date theSavedTime) {
mySavedTime = theSavedTime;
}
public String getDescription() {
return myDescription;
}
public void setDescription(String theDescription) {
myDescription = theDescription;
myDescriptionUpper = StringUtil.normalizeStringForSearchIndexing(theDescription);
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("myId", myId)
.append("myPackageId", myPackageId)
.append("myVersionId", myVersionId)
.append("myDescriptionUpper", myDescriptionUpper)
.append("myFhirVersionId", myFhirVersionId)
.toString();
}
public List<NpmPackageVersionResourceEntity> getResources() {
return myResources;
}
}

View File

@ -0,0 +1,160 @@
package ca.uhn.fhir.jpa.model.entity;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirVersionEnum;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
import java.util.Date;
@Entity()
@Table(name = "NPM_PACKAGE_VER_RES", uniqueConstraints = {
}, indexes = {
@Index(name = "IDX_PACKVERRES_URL", columnList = "CANONICAL_URL")
})
public class NpmPackageVersionResourceEntity {
@Id
@SequenceGenerator(name = "SEQ_NPM_PACKVERRES", sequenceName = "SEQ_NPM_PACKVERRES")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_NPM_PACKVERRES")
@Column(name = "PID")
private Long myId;
@ManyToOne
@JoinColumn(name = "PACKVER_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_NPM_PACKVERRES_PACKVER"), nullable = false)
private NpmPackageVersionEntity myPackageVersion;
@OneToOne
@JoinColumn(name = "BINARY_RES_ID", referencedColumnName = "RES_ID", nullable = false, foreignKey = @ForeignKey(name = "FK_NPM_PKVR_RESID"))
private ResourceTable myResourceBinary;
@Column(name = "FILE_DIR", length = 200)
private String myDirectory;
@Column(name = "FILE_NAME", length = 200)
private String myFilename;
@Column(name = "RES_TYPE", length = ResourceTable.RESTYPE_LEN, nullable = false)
private String myResourceType;
@Column(name = "CANONICAL_URL", length = 200)
private String myCanonicalUrl;
@Column(name = "CANONICAL_VERSION", length = 200)
private String myCanonicalVersion;
@Enumerated(EnumType.STRING)
@Column(name = "FHIR_VERSION", length = NpmPackageVersionEntity.FHIR_VERSION_LENGTH, nullable = false)
private FhirVersionEnum myFhirVersion;
@Column(name = "FHIR_VERSION_ID", length = NpmPackageVersionEntity.FHIR_VERSION_LENGTH, nullable = false)
private String myFhirVersionId;
@Column(name = "RES_SIZE_BYTES", nullable = false)
private long myResSizeBytes;
@Temporal(TemporalType.TIMESTAMP)
@Version
@Column(name = "UPDATED_TIME", nullable = false)
private Date myVersion;
public long getResSizeBytes() {
return myResSizeBytes;
}
public void setResSizeBytes(long theResSizeBytes) {
myResSizeBytes = theResSizeBytes;
}
public String getCanonicalVersion() {
return myCanonicalVersion;
}
public void setCanonicalVersion(String theCanonicalVersion) {
myCanonicalVersion = theCanonicalVersion;
}
public ResourceTable getResourceBinary() {
return myResourceBinary;
}
public void setResourceBinary(ResourceTable theResourceBinary) {
myResourceBinary = theResourceBinary;
}
public String getFhirVersionId() {
return myFhirVersionId;
}
public void setFhirVersionId(String theFhirVersionId) {
myFhirVersionId = theFhirVersionId;
}
public FhirVersionEnum getFhirVersion() {
return myFhirVersion;
}
public void setFhirVersion(FhirVersionEnum theFhirVersion) {
myFhirVersion = theFhirVersion;
}
public void setPackageVersion(NpmPackageVersionEntity thePackageVersion) {
myPackageVersion = thePackageVersion;
}
public String getDirectory() {
return myDirectory;
}
public void setDirectory(String theDirectory) {
myDirectory = theDirectory;
}
public String getFilename() {
return myFilename;
}
public void setFilename(String theFilename) {
myFilename = theFilename;
}
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
public String getCanonicalUrl() {
return myCanonicalUrl;
}
public void setCanonicalUrl(String theCanonicalUrl) {
myCanonicalUrl = theCanonicalUrl;
}
}

View File

@ -24,7 +24,7 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.util.StringNormalizer;
import ca.uhn.fhir.util.StringUtil;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -266,7 +266,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return false;
}
StringParam string = (StringParam) theParam;
String normalizedString = StringNormalizer.normalizeStringForSearchIndexing(defaultString(string.getValue()));
String normalizedString = StringUtil.normalizeStringForSearchIndexing(defaultString(string.getValue()));
return defaultString(getValueNormalized()).startsWith(normalizedString);
}

View File

@ -1,15 +0,0 @@
package ca.uhn.fhir.jpa.model.util;
import ca.uhn.fhir.util.StringNormalizer;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class StringNormalizerTest {
@Test
public void testNormalizeString() {
assertEquals("TEST TEST", StringNormalizer.normalizeStringForSearchIndexing("TEST teSt"));
assertEquals("AEIØU", StringNormalizer.normalizeStringForSearchIndexing("åéîøü"));
assertEquals("杨浩", StringNormalizer.normalizeStringForSearchIndexing("杨浩"));
}
}

View File

@ -0,0 +1,45 @@
package ca.uhn.fhir.jpa.model.util;
import ca.uhn.fhir.util.StringUtil;
import com.google.common.base.Charsets;
import org.junit.Test;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.assertEquals;
public class StringUtilTest {
@Test
public void testNormalizeString() {
assertEquals("TEST TEST", StringUtil.normalizeStringForSearchIndexing("TEST teSt"));
assertEquals("AEIØU", StringUtil.normalizeStringForSearchIndexing("åéîøü"));
assertEquals("杨浩", StringUtil.normalizeStringForSearchIndexing("杨浩"));
assertEquals(null, StringUtil.normalizeStringForSearchIndexing(null));
}
@Test
public void testToStringNoBom() {
String input = "help i'm a bug";
String output = StringUtil.toUtf8String(input.getBytes(Charsets.UTF_8));
assertEquals(input, output);
}
@Test
public void testToStringWithBom() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(bos, StandardCharsets.UTF_8));
out.write('\ufeff');
out.write("help i'm a bug");
out.close();
byte[] bytes = bos.toByteArray();
String output = StringUtil.toUtf8String(bytes);
assertEquals("help i'm a bug", output);
}
}

View File

@ -43,7 +43,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.StringNormalizer;
import ca.uhn.fhir.util.StringUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
@ -1098,7 +1098,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
String searchParamName = theSearchParam.getName();
String valueNormalized = StringNormalizer.normalizeStringForSearchIndexing(value);
String valueNormalized = StringUtil.normalizeStringForSearchIndexing(value);
if (valueNormalized.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
valueNormalized = valueNormalized.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}

View File

@ -19,7 +19,7 @@ import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.util.StringNormalizer;
import ca.uhn.fhir.util.StringUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Sets;
import org.hamcrest.Matchers;
@ -74,7 +74,7 @@ public class SearchParamExtractorDstu3Test {
String value = IntStream.range(1, 200).mapToObj(v -> "a").collect(Collectors.joining()) + "ئ";
assertEquals(value.length(), 200);
assertEquals(Normalizer.normalize(value, Normalizer.Form.NFD).length(), 201);
assertEquals(StringNormalizer.normalizeStringForSearchIndexing(value).length(), 201);
assertEquals(StringUtil.normalizeStringForSearchIndexing(value).length(), 201);
Questionnaire questionnaire = new Questionnaire();
questionnaire.setDescription(value);

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
import org.apache.commons.dbcp2.BasicDataSource;
@ -133,15 +134,16 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
/**
* Bean which validates incoming requests
* @param theInstanceValidator
*/
@Bean
@Lazy
public RequestValidatingInterceptor requestValidatingInterceptor() {
public RequestValidatingInterceptor requestValidatingInterceptor(IValidatorModule theInstanceValidator) {
RequestValidatingInterceptor requestValidator = new RequestValidatingInterceptor();
requestValidator.setFailOnSeverity(null);
requestValidator.setAddResponseHeaderOnSeverity(null);
requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
requestValidator.addValidatorModule(instanceValidator());
requestValidator.addValidatorModule(theInstanceValidator);
requestValidator.setIgnoreValidatorExceptions(true);
return requestValidator;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.empi.rules.similarity;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.empi.util.NameUtil;
import ca.uhn.fhir.util.StringNormalizer;
import ca.uhn.fhir.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBase;
@ -56,10 +56,10 @@ public class NameSimilarity implements IEmpiFieldSimilarity {
List<String> rightGivenNames = NameUtil.extractGivenNames(theFhirContext, theRightBase);
if (!exact) {
leftFamilyName = StringNormalizer.normalizeStringForSearchIndexing(leftFamilyName);
rightFamilyName = StringNormalizer.normalizeStringForSearchIndexing(rightFamilyName);
leftGivenNames = leftGivenNames.stream().map(StringNormalizer::normalizeStringForSearchIndexing).collect(Collectors.toList());
rightGivenNames = rightGivenNames.stream().map(StringNormalizer::normalizeStringForSearchIndexing).collect(Collectors.toList());
leftFamilyName = StringUtil.normalizeStringForSearchIndexing(leftFamilyName);
rightFamilyName = StringUtil.normalizeStringForSearchIndexing(rightFamilyName);
leftGivenNames = leftGivenNames.stream().map(StringUtil::normalizeStringForSearchIndexing).collect(Collectors.toList());
rightGivenNames = rightGivenNames.stream().map(StringUtil::normalizeStringForSearchIndexing).collect(Collectors.toList());
}
for (String leftGivenName : leftGivenNames) {

View File

@ -100,6 +100,7 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import static ca.uhn.fhir.util.StringUtil.toUtf8String;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -920,7 +921,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
if (isIgnoreServerParsedRequestParameters()) {
String contentType = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
if (theRequestType == RequestTypeEnum.POST && isNotBlank(contentType) && contentType.startsWith(Constants.CT_X_FORM_URLENCODED)) {
String requestBody = new String(requestDetails.loadRequestContents(), Constants.CHARSET_UTF8);
String requestBody = toUtf8String(requestDetails.loadRequestContents());
params = UrlUtil.parseQueryStrings(theRequest.getQueryString(), requestBody);
} else if (theRequestType == RequestTypeEnum.GET) {
params = UrlUtil.parseQueryString(theRequest.getQueryString());

View File

@ -18,7 +18,7 @@ spring:
enabled: true
hapi:
fhir:
version: dstu3
version: DSTU3
server:
path: /fhir/*
rest:

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Sets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullWriter;
@ -17,7 +18,9 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.junit.Test;
import static org.junit.Assert.*;
public class ResourceUtilTest {
@Test
public void testRemoveNarrative() {
Bundle bundle = new Bundle();
Patient patient = new Patient();
patient.getText().getDiv().setValue("<div>help im a bug</div>");
bundle.addEntry().setResource(patient);
Bundle embeddedBundle = new Bundle();
embeddedBundle.setType(Bundle.BundleType.COLLECTION);
bundle.addEntry().setResource(embeddedBundle);
ResourceUtil.removeNarrative(FhirContext.forR4(), bundle);
assertNull(((Patient)bundle.getEntry().get(0).getResource()).getText().getDiv().getValueAsString());
}
}

View File

@ -1,21 +1,27 @@
package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@SuppressWarnings("unchecked")
public class CachingValidationSupport extends BaseValidationSupportWrapper implements IValidationSupport {
@ -30,17 +36,17 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
super(theWrap.getFhirContext(), theWrap);
myValidateCodeCache = Caffeine
.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(5000)
.build();
myLookupCodeCache = Caffeine
.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(5000)
.build();
myCache = Caffeine
.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(5000)
.build();
}
@ -96,6 +102,19 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
}
@Override
public IValidationSupport.CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
BaseRuntimeChildDefinition urlChild = myCtx.getResourceDefinition(theValueSet).getChildByName("url");
Optional<String> valueSetUrl = urlChild.getAccessor().getValues(theValueSet).stream().map(t -> ((IPrimitiveType<?>) t).getValueAsString()).filter(t->isNotBlank(t)).findFirst();
if (valueSetUrl.isPresent()) {
String key = "validateCodeInValueSet " + theValidationOptions.toString() + " " + defaultString(theCodeSystem, "(null)") + " " + defaultString(theCode, "(null)") + " " + defaultString(theDisplay, "(null)") + " " + valueSetUrl.get();
return loadFromCache(myValidateCodeCache, key, t-> super.validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, theValueSet));
}
return super.validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, theValueSet);
}
@Override
public void invalidateCaches() {
myLookupCodeCache.invalidateAll();

View File

@ -2,12 +2,10 @@ package ca.uhn.fhir.tinder;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.ResourceUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -15,7 +13,6 @@ import org.apache.maven.plugin.*;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
@ -72,7 +69,7 @@ public class ResourceMinimizerMojo extends AbstractMojo {
String inputString;
try {
inputString = IOUtils.toString(new FileInputStream(nextFile), "UTF-8");
inputString = IOUtils.toString(new FileInputStream(nextFile), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new MojoFailureException("Failed to read file: " + nextFile, e);
}
@ -81,18 +78,18 @@ public class ResourceMinimizerMojo extends AbstractMojo {
IBaseResource input = parser.parseResource(inputString);
if (input instanceof IResource) {
((IResource) input).getText().getDiv().setValueAsString((String) null);
((IResource) input).getText().getStatus().setValueAsString((String) null);
((IResource) input).getText().getDiv().setValueAsString(null);
((IResource) input).getText().getStatus().setValueAsString(null);
if (input instanceof Bundle) {
for (Entry nextEntry : ((Bundle) input).getEntry()) {
if (nextEntry.getResource() != null) {
nextEntry.getResource().getText().getDiv().setValueAsString((String) null);
nextEntry.getResource().getText().getStatus().setValueAsString((String) null);
nextEntry.getResource().getText().getDiv().setValueAsString(null);
nextEntry.getResource().getText().getStatus().setValueAsString(null);
}
}
}
} else {
minimizeResource((IBaseResource)input);
ResourceUtil.removeNarrative(myCtx, input);
}
String outputString = parser.setPrettyPrint(true).encodeResourceToString(input);
@ -117,7 +114,7 @@ public class ResourceMinimizerMojo extends AbstractMojo {
myFileCount++;
try {
String f = nextFile.getAbsolutePath();
Writer w = new OutputStreamWriter(new FileOutputStream(f, false), "UTF-8");
Writer w = new OutputStreamWriter(new FileOutputStream(f, false), StandardCharsets.UTF_8);
w = new BufferedWriter(w);
w.append(outputString);
w.close();
@ -130,20 +127,6 @@ public class ResourceMinimizerMojo extends AbstractMojo {
}
}
private void minimizeResource(IBaseResource theInput) {
if (theInput instanceof IBaseBundle) {
for (IBaseResource next : BundleUtil.toListOfResources(myCtx, (IBaseBundle) theInput)) {
minimizeResource(next);
}
}
BaseRuntimeElementCompositeDefinition<?> element = (BaseRuntimeElementCompositeDefinition) myCtx.getElementDefinition(theInput.getClass());
BaseRuntimeChildDefinition textElement = element.getChildByName("text");
if (textElement != null) {
textElement.getMutator().setValue(theInput, null);
}
}
public long getByteCount() {
return myByteCount;
}

View File

@ -665,7 +665,7 @@
<properties>
<fhir_core_version>5.0.1-SNAPSHOT</fhir_core_version>
<fhir_core_version>5.0.7-SNAPSHOT</fhir_core_version>
<ucum_version>1.0.2</ucum_version>
<surefire_jvm_args>-Dfile.encoding=UTF-8 -Xmx2048m</surefire_jvm_args>
@ -895,7 +895,7 @@
<dependency>
<groupId>org.hl7.fhir.testcases</groupId>
<artifactId>fhir-test-cases</artifactId>
<version>1.1.14-SNAPSHOT</version>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
@ -1009,6 +1009,11 @@
<artifactId>reflow-velocity-tools</artifactId>
<version>2.0.0-beta2</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>