Merge remote-tracking branch 'remotes/origin/master' into ks-flyway
# Conflicts: # hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
This commit is contained in:
commit
18e8604494
|
@ -58,3 +58,5 @@ jobs:
|
|||
#checkStyleRunAnalysis: false # Optional
|
||||
#pmdRunAnalysis: false # Optional
|
||||
#findBugsRunAnalysis: false # Optional
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package example;
|
||||
|
||||
import org.hl7.fhir.converter.NullVersionConverterAdvisor30;
|
||||
import org.hl7.fhir.convertors.*;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
package example;
|
||||
|
||||
import ca.uhn.fhir.model.api.Tag;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import org.hl7.fhir.r4.model.InstantType;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.api.Tag;
|
||||
import ca.uhn.fhir.model.api.TagList;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
|
||||
public class ServerMetadataExamples {
|
||||
|
||||
// START SNIPPET: serverMethod
|
||||
|
@ -21,34 +19,21 @@ public class ServerMetadataExamples {
|
|||
Patient patient = new Patient();
|
||||
retVal.add(patient);
|
||||
patient.setId("Patient/123");
|
||||
patient.addName().addFamily("Smith").addGiven("John");
|
||||
|
||||
// Create a tag list and add it to the resource
|
||||
TagList tags = new TagList();
|
||||
tags.addTag(Tag.HL7_ORG_FHIR_TAG, "http://foo/tag1.html", "Some tag");
|
||||
tags.addTag(Tag.HL7_ORG_FHIR_TAG, "http://foo/tag2.html", "Another tag");
|
||||
ResourceMetadataKeyEnum.TAG_LIST.put(patient, tags);
|
||||
|
||||
// Set some links (these can be provided as relative links or absolute)
|
||||
// and the server will convert to absolute as appropriate
|
||||
String linkAlternate = "Patient/7736";
|
||||
ResourceMetadataKeyEnum.LINK_ALTERNATE.put(patient, linkAlternate);
|
||||
String linkSearch = "Patient?name=smith&name=john";
|
||||
ResourceMetadataKeyEnum.LINK_SEARCH.put(patient, linkSearch);
|
||||
|
||||
// Set the published and updated dates
|
||||
InstantDt pubDate = new InstantDt("2011-02-22");
|
||||
ResourceMetadataKeyEnum.PUBLISHED.put(patient, pubDate);
|
||||
InstantDt updatedDate = new InstantDt("2014-07-12T11:22:27Z");
|
||||
ResourceMetadataKeyEnum.UPDATED.put(patient, updatedDate);
|
||||
|
||||
// Set the resource title (note that if you are using HAPI's narrative
|
||||
// generation capability, the narrative generator will often create
|
||||
// useful titles automatically, and the server will create a default
|
||||
// title if none is provided)
|
||||
String title = "Patient John SMITH";
|
||||
ResourceMetadataKeyEnum.TITLE.put(patient, title);
|
||||
patient.addName().setFamily("Smith").addGiven("John");
|
||||
|
||||
// Add tags
|
||||
patient.getMeta().addTag()
|
||||
.setSystem(Tag.HL7_ORG_FHIR_TAG)
|
||||
.setCode("some_tag")
|
||||
.setDisplay("Some tag");
|
||||
patient.getMeta().addTag()
|
||||
.setSystem(Tag.HL7_ORG_FHIR_TAG)
|
||||
.setCode("another_tag")
|
||||
.setDisplay("Another tag");
|
||||
|
||||
// Set the last updated date
|
||||
patient.getMeta().setLastUpdatedElement(new InstantType("2011-02-22T11:22:00.0122Z"));
|
||||
|
||||
return retVal;
|
||||
}
|
||||
// END SNIPPET: serverMethod
|
||||
|
|
|
@ -36,9 +36,12 @@ public class ServerOperations {
|
|||
public void manualInputAndOutput(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws IOException {
|
||||
String contentType = theServletRequest.getContentType();
|
||||
byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream());
|
||||
|
||||
ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length);
|
||||
|
||||
// In a real example we might do something more interesting with the received bytes,
|
||||
// here we'll just replace them with hardcoded ones
|
||||
bytes = new byte[] { 0, 1, 2, 3 };
|
||||
|
||||
theServletResponse.setContentType(contentType);
|
||||
theServletResponse.getOutputStream().write(bytes);
|
||||
theServletResponse.getOutputStream().close();
|
||||
|
|
|
@ -265,7 +265,7 @@ public class ValidatorExamples {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) {
|
||||
// TODO: implement
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -100,10 +100,6 @@
|
|||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
|
@ -120,6 +116,11 @@
|
|||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import ca.uhn.fhir.util.ReflectionUtil;
|
|||
import ca.uhn.fhir.util.VersionUtil;
|
||||
import ca.uhn.fhir.validation.FhirValidator;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.jena.riot.Lang;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
|
@ -896,6 +897,11 @@ public class FhirContext {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FhirContext[" + myVersion.getVersion().name() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2}
|
||||
*/
|
||||
|
|
|
@ -29,6 +29,7 @@ import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
|||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
|
@ -44,56 +45,47 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
class ModelScanner {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
|
||||
|
||||
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
|
||||
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<>();
|
||||
private FhirContext myContext;
|
||||
private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
|
||||
private Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinitions = new HashMap<String, BaseRuntimeElementDefinition<?>>();
|
||||
private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<String, RuntimeResourceDefinition>();
|
||||
private Map<String, Class<? extends IBaseResource>> myNameToResourceType = new HashMap<String, Class<? extends IBaseResource>>();
|
||||
private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = new HashMap<>();
|
||||
private Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinitions = new HashMap<>();
|
||||
private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<>();
|
||||
private Map<String, Class<? extends IBaseResource>> myNameToResourceType = new HashMap<>();
|
||||
private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
|
||||
private Set<Class<? extends IBase>> myScanAlso = new HashSet<Class<? extends IBase>>();
|
||||
private Set<Class<? extends IBase>> myScanAlso = new HashSet<>();
|
||||
private FhirVersionEnum myVersion;
|
||||
|
||||
private Set<Class<? extends IBase>> myVersionTypes;
|
||||
|
||||
ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions,
|
||||
Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
|
||||
@Nonnull Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
|
||||
myContext = theContext;
|
||||
myVersion = theVersion;
|
||||
Set<Class<? extends IBase>> toScan;
|
||||
if (theResourceTypes != null) {
|
||||
toScan = new HashSet<Class<? extends IBase>>(theResourceTypes);
|
||||
} else {
|
||||
toScan = new HashSet<Class<? extends IBase>>();
|
||||
}
|
||||
Set<Class<? extends IBase>> toScan = new HashSet<>(theResourceTypes);
|
||||
init(theExistingDefinitions, toScan);
|
||||
}
|
||||
|
||||
public Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
|
||||
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
|
||||
return myClassToElementDefinitions;
|
||||
}
|
||||
|
||||
public Map<String, RuntimeResourceDefinition> getIdToResourceDefinition() {
|
||||
Map<String, RuntimeResourceDefinition> getIdToResourceDefinition() {
|
||||
return myIdToResourceDefinition;
|
||||
}
|
||||
|
||||
public Map<String, BaseRuntimeElementDefinition<?>> getNameToElementDefinitions() {
|
||||
Map<String, BaseRuntimeElementDefinition<?>> getNameToElementDefinitions() {
|
||||
return myNameToElementDefinitions;
|
||||
}
|
||||
|
||||
public Map<String, RuntimeResourceDefinition> getNameToResourceDefinition() {
|
||||
Map<String, RuntimeResourceDefinition> getNameToResourceDefinition() {
|
||||
return myNameToResourceDefinitions;
|
||||
}
|
||||
|
||||
public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() {
|
||||
return (myNameToResourceDefinitions);
|
||||
}
|
||||
|
||||
public Map<String, Class<? extends IBaseResource>> getNameToResourceType() {
|
||||
Map<String, Class<? extends IBaseResource>> getNameToResourceType() {
|
||||
return myNameToResourceType;
|
||||
}
|
||||
|
||||
public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
|
||||
RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
|
||||
return myRuntimeChildUndeclaredExtensionDefinition;
|
||||
}
|
||||
|
||||
|
@ -145,11 +137,10 @@ class ModelScanner {
|
|||
}
|
||||
|
||||
private boolean isStandardType(Class<? extends IBase> theClass) {
|
||||
boolean retVal = myVersionTypes.contains(theClass);
|
||||
return retVal;
|
||||
return myVersionTypes.contains(theClass);
|
||||
}
|
||||
|
||||
private void scan(Class<? extends IBase> theClass) throws ConfigurationException {
|
||||
void scan(Class<? extends IBase> theClass) throws ConfigurationException {
|
||||
BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass);
|
||||
if (existingDef != null) {
|
||||
return;
|
||||
|
@ -204,9 +195,6 @@ class ModelScanner {
|
|||
ourLog.debug("Scanning resource block class: {}", theClass.getName());
|
||||
|
||||
String resourceName = theClass.getCanonicalName();
|
||||
if (isBlank(resourceName)) {
|
||||
throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
|
||||
}
|
||||
|
||||
// Just in case someone messes up when upgrading from DSTU2
|
||||
if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
|
||||
|
@ -341,14 +329,14 @@ class ModelScanner {
|
|||
|
||||
private void scanResourceForSearchParams(Class<? extends IBaseResource> theClass, RuntimeResourceDefinition theResourceDef) {
|
||||
|
||||
Map<String, RuntimeSearchParam> nameToParam = new HashMap<String, RuntimeSearchParam>();
|
||||
Map<Field, SearchParamDefinition> compositeFields = new LinkedHashMap<Field, SearchParamDefinition>();
|
||||
Map<String, RuntimeSearchParam> nameToParam = new HashMap<>();
|
||||
Map<Field, SearchParamDefinition> compositeFields = new LinkedHashMap<>();
|
||||
|
||||
/*
|
||||
* Make sure we pick up fields in interfaces too.. This ensures that we
|
||||
* grab the _id field which generally gets picked up via interface
|
||||
*/
|
||||
Set<Field> fields = new HashSet<Field>(Arrays.asList(theClass.getFields()));
|
||||
Set<Field> fields = new HashSet<>(Arrays.asList(theClass.getFields()));
|
||||
Class<?> nextClass = theClass;
|
||||
do {
|
||||
for (Class<?> nextInterface : nextClass.getInterfaces()) {
|
||||
|
@ -400,12 +388,12 @@ class ModelScanner {
|
|||
for (Entry<Field, SearchParamDefinition> nextEntry : compositeFields.entrySet()) {
|
||||
SearchParamDefinition searchParam = nextEntry.getValue();
|
||||
|
||||
List<RuntimeSearchParam> compositeOf = new ArrayList<RuntimeSearchParam>();
|
||||
List<RuntimeSearchParam> compositeOf = new ArrayList<>();
|
||||
for (String nextName : searchParam.compositeOf()) {
|
||||
RuntimeSearchParam param = nameToParam.get(nextName);
|
||||
if (param == null) {
|
||||
ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}",
|
||||
new Object[]{theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()});
|
||||
theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet());
|
||||
continue;
|
||||
}
|
||||
compositeOf.add(param);
|
||||
|
@ -417,7 +405,7 @@ class ModelScanner {
|
|||
}
|
||||
|
||||
private Set<String> toTargetList(Class<? extends IBaseResource>[] theTarget) {
|
||||
HashSet<String> retVal = new HashSet<String>();
|
||||
HashSet<String> retVal = new HashSet<>();
|
||||
|
||||
for (Class<? extends IBaseResource> nextType : theTarget) {
|
||||
ResourceDef resourceDef = nextType.getAnnotation(ResourceDef.class);
|
||||
|
@ -486,7 +474,7 @@ class ModelScanner {
|
|||
}
|
||||
|
||||
static Set<Class<? extends IBase>> scanVersionPropertyFile(Set<Class<? extends IBase>> theDatatypes, Map<String, Class<? extends IBaseResource>> theResourceTypes, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingElementDefinitions) {
|
||||
Set<Class<? extends IBase>> retVal = new HashSet<Class<? extends IBase>>();
|
||||
Set<Class<? extends IBase>> retVal = new HashSet<>();
|
||||
|
||||
try (InputStream str = theVersion.getVersionImplementation().getFhirVersionPropertiesFile()) {
|
||||
Properties prop = new Properties();
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
package ca.uhn.fhir.context;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 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.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.model.api.annotation.ProvidesResources;
|
||||
|
||||
/**
|
||||
* Scans a class tagged with {@code ProvidesResources} and adds any resources listed to its FhirContext's resource
|
||||
* definition list. This makes the profile generator find the classes.
|
||||
*
|
||||
* @see ca.uhn.fhir.model.api.annotation.ProvidesResources
|
||||
*/
|
||||
public class ProvidedResourceScanner {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ProvidedResourceScanner.class);
|
||||
private FhirContext myContext;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theContext
|
||||
* - context whose resource definition list is to be updated by the scanner
|
||||
*/
|
||||
public ProvidedResourceScanner(FhirContext theContext) {
|
||||
myContext = theContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code theProvider} is tagged with the {@code ProvidesResources} annotation, this method will add every
|
||||
* resource listed by the {@code resources} method.
|
||||
* <p>
|
||||
* Notes:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>if {@code theProvider} isn't annotated with {@code resources} nothing is done; it's expected that most
|
||||
* RestfulServers and ResourceProviders won't be annotated.</li>
|
||||
* <li>any object listed in {@code resources} that doesn't implement {@code IResource} will generate a warning in the
|
||||
* log.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param theProvider
|
||||
* - Normally, either a {@link ca.uhn.fhir.rest.server.RestfulServer} or a
|
||||
* {@link ca.uhn.fhir.rest.server.IResourceProvider} that might be annotated with
|
||||
* {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void scanForProvidedResources(Object theProvider) {
|
||||
ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
|
||||
if (annotation == null)
|
||||
return;
|
||||
for (Class<?> clazz : annotation.resources()) {
|
||||
if (IBaseResource.class.isAssignableFrom(clazz)) {
|
||||
myContext.getResourceDefinition((Class<? extends IBaseResource>) clazz);
|
||||
} else {
|
||||
ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any metadata that was added by any {@code ProvidesResources} annotation
|
||||
* present in {@code theProvider}. This method is callled from {@code RestfulService}
|
||||
* when it is unregistering a Resource Provider.
|
||||
*
|
||||
* @param theProvider
|
||||
* - Normally a {@link ca.uhn.fhir.rest.server.IResourceProvider} that might
|
||||
* be annotated with {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
|
||||
*/
|
||||
public void removeProvidedResources(Object theProvider) {
|
||||
ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
|
||||
if (annotation == null)
|
||||
return;
|
||||
for (Class<?> clazz : annotation.resources()) {
|
||||
if (IBaseResource.class.isAssignableFrom(clazz)) {
|
||||
// TODO -- not currently used but should be finished for completeness
|
||||
} else {
|
||||
ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -102,6 +102,11 @@ public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST>
|
|||
*/
|
||||
boolean isCodeSystemSupported(FhirContext theContext, String theSystem);
|
||||
|
||||
/**
|
||||
* Fetch the given ValueSet by URL
|
||||
*/
|
||||
IBaseResource fetchValueSet(FhirContext theContext, String theValueSetUrl);
|
||||
|
||||
/**
|
||||
* Validates that the given code exists and if possible returns a display
|
||||
* name. This method is called to check codes which are found in "example"
|
||||
|
@ -112,7 +117,7 @@ public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST>
|
|||
* @param theDisplay The display name, if it should also be validated
|
||||
* @return Returns a validation result object
|
||||
*/
|
||||
CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
|
||||
CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl);
|
||||
|
||||
/**
|
||||
* Look up a code using the system and code value
|
||||
|
|
|
@ -21,7 +21,6 @@ package ca.uhn.fhir.model.api;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
|
||||
import ca.uhn.fhir.model.primitive.DecimalDt;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
|
||||
|
@ -29,7 +28,6 @@ import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
@ -92,29 +90,7 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
theResource.setUserData(DELETED_AT.name(), theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Denotes the search score which a given resource should match in a transaction. See the FHIR transaction definition for information about this. Corresponds to the value in
|
||||
* <code>Bundle.entry.score</code> in a Bundle resource.
|
||||
* <p>
|
||||
* Note that search URL is only used in FHIR DSTU2 and later.
|
||||
* </p>
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link DecimalDt}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<DecimalDt> ENTRY_SCORE = new ResourceMetadataKeyEnum<DecimalDt>("ENTRY_SCORE") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public DecimalDt get(IResource theResource) {
|
||||
return getDecimalFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ENTRY_SCORE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, DecimalDt theObject) {
|
||||
theResource.getResourceMetadata().put(ENTRY_SCORE, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* If present and populated with a {@link BundleEntrySearchModeEnum}, contains the "bundle entry search mode", which is the value of the status field in the Bundle entry containing this resource.
|
||||
* The value for this key corresponds to field <code>Bundle.entry.search.mode</code>. This value can be set to provide a status value of "include" for included resources being returned by a
|
||||
|
@ -187,67 +163,7 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
}
|
||||
|
||||
};
|
||||
/**
|
||||
* If present and populated with a string, provides the "alternate link" (the link element in the bundle entry with <code>rel="alternate"</code>). Server implementations may populate this with a
|
||||
* complete URL, in which case the URL will be placed as-is in the bundle. They may alternately specify a resource relative URL (e.g. "Patient/1243") in which case the server will convert this to
|
||||
* an absolute URL at runtime.
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link String}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<String> LINK_ALTERNATE = new ResourceMetadataKeyEnum<String>("LINK_ALTERNATE") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public String get(IResource theResource) {
|
||||
return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), LINK_ALTERNATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, String theObject) {
|
||||
theResource.getResourceMetadata().put(LINK_ALTERNATE, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* If present and populated with a string, provides the "search link" (the link element in the bundle entry with <code>rel="search"</code>). Server implementations may populate this with a
|
||||
* complete URL, in which case the URL will be placed as-is in the bundle. They may alternately specify a resource relative URL (e.g. "Patient?name=tester") in which case the server will convert
|
||||
* this to an absolute URL at runtime.
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link String}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<String> LINK_SEARCH = new ResourceMetadataKeyEnum<String>("LINK_SEARCH") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public String get(IResource theResource) {
|
||||
return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), LINK_SEARCH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, String theObject) {
|
||||
theResource.getResourceMetadata().put(LINK_SEARCH, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The value for this key represents a previous ID used to identify this resource. This key is currently only used internally during transaction method processing.
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link IdDt}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<IdDt> PREVIOUS_ID = new ResourceMetadataKeyEnum<IdDt>("PREVIOUS_ID") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public IdDt get(IResource theResource) {
|
||||
return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata(), PREVIOUS_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, IdDt theObject) {
|
||||
theResource.getResourceMetadata().put(PREVIOUS_ID, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The value for this key represents a {@link List} of profile IDs that this resource claims to conform to.
|
||||
* <p>
|
||||
|
@ -301,18 +217,8 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<BaseCodingDt> securityLabels = (List<BaseCodingDt>) obj;
|
||||
if (securityLabels.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return securityLabels;
|
||||
} catch (ClassCastException e) {
|
||||
throw new InternalErrorException("Found an object of type '" + obj.getClass().getCanonicalName() + "' in resource metadata for key SECURITY_LABELS - Expected "
|
||||
+ BaseCodingDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
//noinspection unchecked
|
||||
return (List<BaseCodingDt>) obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -337,14 +243,9 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
Object retValObj = theResource.getResourceMetadata().get(TAG_LIST);
|
||||
if (retValObj == null) {
|
||||
return null;
|
||||
} else if (retValObj instanceof TagList) {
|
||||
if (((TagList) retValObj).isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return (TagList) retValObj;
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + TAG_LIST.name() + " - Expected "
|
||||
+ TagList.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -352,25 +253,6 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
theResource.getResourceMetadata().put(TAG_LIST, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* If present and populated with a string (as an instance of {@link String}), this value contains the title for this resource, as supplied in any bundles containing the resource.
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link String}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<String> TITLE = new ResourceMetadataKeyEnum<String>("TITLE") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public String get(IResource theResource) {
|
||||
return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), TITLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, String theObject) {
|
||||
theResource.getResourceMetadata().put(TITLE, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The value for this key is the bundle entry <b>Updated</b> time. This is defined by FHIR as "Last Updated for resource". This value is also used for populating the "Last-Modified" header in the
|
||||
* case of methods that return a single resource (read, vread, etc.)
|
||||
|
@ -398,7 +280,10 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
* <p>
|
||||
* Values for this key are of type <b>{@link String}</b>
|
||||
* </p>
|
||||
*
|
||||
* @deprecated The {@link IResource#getId()} resource ID will now be populated with the version ID via the {@link IdDt#getVersionIdPart()} method
|
||||
*/
|
||||
@Deprecated
|
||||
public static final ResourceMetadataKeyEnum<String> VERSION = new ResourceMetadataKeyEnum<String>("VERSION") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
@ -426,7 +311,7 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
|
||||
@Override
|
||||
public IdDt get(IResource theResource) {
|
||||
return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata(), VERSION_ID);
|
||||
return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -474,32 +359,45 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
|
||||
public abstract void put(IResource theResource, T theObject);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myValue;
|
||||
public static abstract class ResourceMetadataKeySupportingAnyResource<T, T2> extends ResourceMetadataKeyEnum<T> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ResourceMetadataKeySupportingAnyResource(String theValue) {
|
||||
super(theValue);
|
||||
}
|
||||
|
||||
public abstract T2 get(IAnyResource theResource);
|
||||
|
||||
public abstract void put(IAnyResource theResource, T2 theObject);
|
||||
|
||||
}
|
||||
|
||||
private static DecimalDt getDecimalFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<DecimalDt> theKey) {
|
||||
Object retValObj = theResourceMetadata.get(theKey);
|
||||
if (retValObj == null) {
|
||||
return null;
|
||||
} else if (retValObj instanceof DecimalDt) {
|
||||
if (((DecimalDt) retValObj).isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return (DecimalDt) retValObj;
|
||||
} else if (retValObj instanceof String) {
|
||||
if (StringUtils.isBlank((String) retValObj)) {
|
||||
return null;
|
||||
}
|
||||
return new DecimalDt((String) retValObj);
|
||||
} else if (retValObj instanceof Double) {
|
||||
return new DecimalDt((Double) retValObj);
|
||||
public static final class ExtensionResourceMetadataKey extends ResourceMetadataKeyEnum<ExtensionDt> {
|
||||
public ExtensionResourceMetadataKey(String theUrl) {
|
||||
super(theUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionDt get(IResource theResource) {
|
||||
Object retValObj = theResource.getResourceMetadata().get(this);
|
||||
if (retValObj == null) {
|
||||
return null;
|
||||
} else if (retValObj instanceof ExtensionDt) {
|
||||
return (ExtensionDt) retValObj;
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName()
|
||||
+ "' in resource metadata for key " + this.name() + " - Expected "
|
||||
+ ExtensionDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, ExtensionDt theObject) {
|
||||
theResource.getResourceMetadata().put(this, theObject);
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
|
||||
+ InstantDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends Enum<?>> T getEnumFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<T> theKey, Class<T> theEnumType,
|
||||
IValueSetEnumBinder<T> theBinder) {
|
||||
|
@ -515,8 +413,8 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
+ InstantDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
private static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<?> theKey) {
|
||||
return toId(theKey, theResourceMetadata.get(theKey));
|
||||
private static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata) {
|
||||
return toId(ResourceMetadataKeyEnum.VERSION_ID, theResourceMetadata.get(ResourceMetadataKeyEnum.VERSION_ID));
|
||||
}
|
||||
|
||||
private static List<IdDt> getIdListFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<?> theKey) {
|
||||
|
@ -586,49 +484,11 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
}
|
||||
return (IdDt) retValObj;
|
||||
} else if (retValObj instanceof Number) {
|
||||
return new IdDt(((Number) retValObj).toString());
|
||||
return new IdDt(retValObj.toString());
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
|
||||
+ IdDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
public static abstract class ResourceMetadataKeySupportingAnyResource<T, T2> extends ResourceMetadataKeyEnum<T> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ResourceMetadataKeySupportingAnyResource(String theValue) {
|
||||
super(theValue);
|
||||
}
|
||||
|
||||
public abstract T2 get(IAnyResource theResource);
|
||||
|
||||
public abstract void put(IAnyResource theResource, T2 theObject);
|
||||
|
||||
}
|
||||
|
||||
public static final class ExtensionResourceMetadataKey extends ResourceMetadataKeyEnum<ExtensionDt> {
|
||||
public ExtensionResourceMetadataKey(String url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionDt get(IResource theResource) {
|
||||
Object retValObj = theResource.getResourceMetadata().get(this);
|
||||
if (retValObj == null) {
|
||||
return null;
|
||||
} else if (retValObj instanceof ExtensionDt) {
|
||||
return (ExtensionDt) retValObj;
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName()
|
||||
+ "' in resource metadata for key " + this.name() + " - Expected "
|
||||
+ ExtensionDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, ExtensionDt theObject) {
|
||||
theResource.getResourceMetadata().put(this, theObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -168,18 +168,6 @@ public class Tag extends BaseElement implements IElement, IBaseCoding {
|
|||
return this;
|
||||
}
|
||||
|
||||
public String toHeaderValue() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(this.getTerm());
|
||||
if (isNotBlank(this.getLabel())) {
|
||||
b.append("; label=\"").append(this.getLabel()).append('"');
|
||||
}
|
||||
if (isNotBlank(this.getScheme())) {
|
||||
b.append("; scheme=\"").append(this.getScheme()).append('"');
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package ca.uhn.fhir.model.api.annotation;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 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.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* IResourceProvider and RestfulServer subclasses can use this annotation to designate which custom resources they can provide.
|
||||
* These resources will automatically be added to the resource list used for profile generation.
|
||||
* <pre>
|
||||
* Examples:
|
||||
* {@literal @}ProvidesResources(resource=CustomObservation.class)
|
||||
* class CustomObservationResourceProvider implements IResourceProvider{...}
|
||||
*
|
||||
* {@literal @}ProvidesResources(resource={CustomPatient.class,CustomObservation.class}){...}
|
||||
* class FhirServer extends RestfulServer
|
||||
* }
|
||||
* </pre>
|
||||
* Note that you needn't annotate both the IResourceProvider and the RestfulServer for a given resource; either one will suffice.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ProvidesResources {
|
||||
Class<?>[] resources();
|
||||
}
|
|
@ -36,5 +36,7 @@ import java.lang.annotation.Target;
|
|||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value=ElementType.METHOD)
|
||||
public @interface Metadata {
|
||||
// nothing for now
|
||||
|
||||
long cacheMillis() default 60 * 1000L;
|
||||
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ public class Constants {
|
|||
|
||||
public static final String CT_TEXT_CSV = "text/csv";
|
||||
public static final String HEADER_REQUEST_ID = "X-Request-ID";
|
||||
public static final String HEADER_REQUEST_SOURCE = "X-Request-Source";
|
||||
public static final String CACHE_CONTROL_MAX_RESULTS = "max-results";
|
||||
public static final String CACHE_CONTROL_NO_CACHE = "no-cache";
|
||||
public static final String CACHE_CONTROL_NO_STORE = "no-store";
|
||||
|
@ -242,6 +243,15 @@ public class Constants {
|
|||
* Operation name for the $lastn operation
|
||||
*/
|
||||
public static final String OPERATION_LASTN = "$lastn";
|
||||
/**
|
||||
* <p>
|
||||
* This extension represents the equivalent of the
|
||||
* <code>Resource.meta.source</code> field within R4+ resources, and is for
|
||||
* use in DSTU3 resources. It should contain a value of type <code>uri</code>
|
||||
* and will be located on the Resource.meta
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
|
||||
|
||||
static {
|
||||
CHARSET_UTF8 = StandardCharsets.UTF_8;
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
package ca.uhn.fhir.rest.api;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
/*
|
||||
|
@ -22,13 +29,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
||||
public class QualifiedParamList extends ArrayList<String> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -83,16 +83,16 @@ public class QualifiedParamList extends ArrayList<String> {
|
|||
prev = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (str.equals(",")) {
|
||||
if (countTrailingSlashes(prev) % 2 == 1) {
|
||||
int idx = retVal.size() - 1;
|
||||
String existing = retVal.get(idx);
|
||||
prev = existing.substring(0, existing.length() - 1) + ',';
|
||||
retVal.set(idx, prev);
|
||||
} else {
|
||||
prev = null;
|
||||
}
|
||||
if (countTrailingSlashes(prev) % 2 == 1) {
|
||||
int idx = retVal.size() - 1;
|
||||
String existing = retVal.get(idx);
|
||||
prev = existing.substring(0, existing.length() - 1) + ',';
|
||||
retVal.set(idx, prev);
|
||||
} else {
|
||||
prev = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -108,11 +108,18 @@ public class QualifiedParamList extends ArrayList<String> {
|
|||
|
||||
}
|
||||
|
||||
// If no value was found, at least add that empty string as a value. It should get ignored later, but at
|
||||
// least this lets us give a sensible error message if the parameter name was bad. See
|
||||
// ResourceProviderR4Test#testParameterWithNoValueThrowsError_InvalidChainOnCustomSearch for an example
|
||||
if (retVal.size() == 0) {
|
||||
retVal.add("");
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private static int countTrailingSlashes(String theString) {
|
||||
if(theString==null) {
|
||||
if (theString == null) {
|
||||
return 0;
|
||||
}
|
||||
int retVal = 0;
|
||||
|
|
|
@ -89,7 +89,7 @@ public enum RestSearchParameterTypeEnum {
|
|||
*/
|
||||
HAS("string", "http://hl7.org/fhir/search-param-type"),
|
||||
|
||||
/**
|
||||
/**
|
||||
* Code Value: <b>number</b>
|
||||
*
|
||||
* Search parameter SHALL be a number (a whole number, or a decimal).
|
||||
|
|
|
@ -33,12 +33,6 @@ import java.util.Map;
|
|||
*/
|
||||
public interface IHttpResponse {
|
||||
|
||||
/**
|
||||
* @deprecated This method was deprecated in HAPI FHIR 2.2 because its name has a typo. Use {@link #bufferEntity()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void bufferEntitity() throws IOException;
|
||||
|
||||
/**
|
||||
* Buffer the message entity data.
|
||||
* <p>
|
||||
|
|
|
@ -61,43 +61,43 @@ public class ParameterUtil {
|
|||
* This is a utility method intended provided to help the JPA module.
|
||||
*/
|
||||
public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RestSearchParameterTypeEnum paramType,
|
||||
String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
|
||||
String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
|
||||
QueryParameterAndBinder binder = null;
|
||||
switch (paramType) {
|
||||
case COMPOSITE:
|
||||
throw new UnsupportedOperationException();
|
||||
case DATE:
|
||||
binder = new QueryParameterAndBinder(DateAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case NUMBER:
|
||||
binder = new QueryParameterAndBinder(NumberAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case QUANTITY:
|
||||
binder = new QueryParameterAndBinder(QuantityAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case REFERENCE:
|
||||
binder = new QueryParameterAndBinder(ReferenceAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case STRING:
|
||||
binder = new QueryParameterAndBinder(StringAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case TOKEN:
|
||||
binder = new QueryParameterAndBinder(TokenAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case URI:
|
||||
binder = new QueryParameterAndBinder(UriAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case HAS:
|
||||
binder = new QueryParameterAndBinder(HasAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case COMPOSITE:
|
||||
throw new UnsupportedOperationException();
|
||||
case DATE:
|
||||
binder = new QueryParameterAndBinder(DateAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case NUMBER:
|
||||
binder = new QueryParameterAndBinder(NumberAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case QUANTITY:
|
||||
binder = new QueryParameterAndBinder(QuantityAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case REFERENCE:
|
||||
binder = new QueryParameterAndBinder(ReferenceAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case STRING:
|
||||
binder = new QueryParameterAndBinder(StringAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case TOKEN:
|
||||
binder = new QueryParameterAndBinder(TokenAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case URI:
|
||||
binder = new QueryParameterAndBinder(UriAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case HAS:
|
||||
binder = new QueryParameterAndBinder(HasAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME null access
|
||||
|
@ -108,7 +108,7 @@ public class ParameterUtil {
|
|||
* This is a utility method intended provided to help the JPA module.
|
||||
*/
|
||||
public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RuntimeSearchParam theParamDef,
|
||||
String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
|
||||
String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
|
||||
RestSearchParameterTypeEnum paramType = theParamDef.getParamType();
|
||||
return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters);
|
||||
}
|
||||
|
@ -126,14 +126,14 @@ public class ParameterUtil {
|
|||
for (int i = 0; i < theValue.length(); i++) {
|
||||
char next = theValue.charAt(i);
|
||||
switch (next) {
|
||||
case '$':
|
||||
case ',':
|
||||
case '|':
|
||||
case '\\':
|
||||
b.append('\\');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case '$':
|
||||
case ',':
|
||||
case '|':
|
||||
case '\\':
|
||||
b.append('\\');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
b.append(next);
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ public class ParameterUtil {
|
|||
|
||||
public static boolean isBindableIntegerType(Class<?> theClass) {
|
||||
return Integer.class.isAssignableFrom(theClass)
|
||||
|| IPrimitiveType.class.isAssignableFrom(theClass);
|
||||
|| IPrimitiveType.class.isAssignableFrom(theClass);
|
||||
}
|
||||
|
||||
public static String escapeAndJoinOrList(Collection<String> theValues) {
|
||||
|
@ -236,7 +236,7 @@ public class ParameterUtil {
|
|||
if (value.charAt(0) == '"') {
|
||||
eTagVersion = value.substring(1, value.length() - 1);
|
||||
} else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/'
|
||||
&& value.charAt(2) == '"') {
|
||||
&& value.charAt(2) == '"') {
|
||||
eTagVersion = value.substring(3, value.length() - 1);
|
||||
} else {
|
||||
eTagVersion = value;
|
||||
|
@ -262,16 +262,16 @@ public class ParameterUtil {
|
|||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName,
|
||||
QualifiedParamList theParameters) {
|
||||
QualifiedParamList theParameters) {
|
||||
if (theParameters.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (theParameters.size() > 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Type " + theParam.getClass().getCanonicalName() + " does not support multiple values");
|
||||
"Type " + theParam.getClass().getCanonicalName() + " does not support multiple values");
|
||||
}
|
||||
theParam.setValueAsQueryToken(theContext, theParamName, theParameters.getQualifier(),
|
||||
theParameters.get(0));
|
||||
theParameters.get(0));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -351,13 +351,13 @@ public class ParameterUtil {
|
|||
b.append(next);
|
||||
} else {
|
||||
switch (theValue.charAt(i + 1)) {
|
||||
case '$':
|
||||
case ',':
|
||||
case '|':
|
||||
case '\\':
|
||||
continue;
|
||||
default:
|
||||
b.append(next);
|
||||
case '$':
|
||||
case ',':
|
||||
case '|':
|
||||
case '\\':
|
||||
continue;
|
||||
default:
|
||||
b.append(next);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -248,26 +248,30 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ {
|
|||
|
||||
@Override
|
||||
public boolean equals(Object theO) {
|
||||
if (this == theO) return true;
|
||||
if (this == theO) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (theO == null || getClass() != theO.getClass()) return false;
|
||||
if (theO == null || getClass() != theO.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TokenParam that = (TokenParam) theO;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(myModifier, that.myModifier)
|
||||
.append(mySystem, that.mySystem)
|
||||
.append(myValue, that.myValue)
|
||||
.isEquals();
|
||||
EqualsBuilder b = new EqualsBuilder();
|
||||
b.append(myModifier, that.myModifier);
|
||||
b.append(mySystem, that.mySystem);
|
||||
b.append(myValue, that.myValue);
|
||||
return b.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(myModifier)
|
||||
.append(mySystem)
|
||||
.append(myValue)
|
||||
.toHashCode();
|
||||
HashCodeBuilder b = new HashCodeBuilder(17, 37);
|
||||
b.append(myModifier);
|
||||
b.append(mySystem);
|
||||
b.append(myValue);
|
||||
return b.toHashCode();
|
||||
}
|
||||
|
||||
private static String toSystemValue(UriDt theSystem) {
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 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.rest.server.exceptions.InternalErrorException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AsyncUtil {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(AsyncUtil.class);
|
||||
|
||||
/**
|
||||
* Non instantiable
|
||||
*/
|
||||
private AsyncUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Thread.sleep and if an InterruptedException occurs, logs a warning but otherwise continues
|
||||
*
|
||||
* @param theMillis The number of millis to sleep
|
||||
* @return Did we sleep the whole amount
|
||||
*/
|
||||
public static boolean sleep(long theMillis) {
|
||||
try {
|
||||
Thread.sleep(theMillis);
|
||||
return true;
|
||||
} catch (InterruptedException theE) {
|
||||
Thread.currentThread().interrupt();
|
||||
ourLog.warn("Sleep for {}ms was interrupted", theMillis);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean awaitLatchAndThrowInternalErrorExceptionOnInterrupt(CountDownLatch theInitialCollectionLatch, long theTime, TimeUnit theTimeUnit) {
|
||||
try {
|
||||
return theInitialCollectionLatch.await(theTime, theTimeUnit);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean awaitLatchAndIgnoreInterrupt(CountDownLatch theInitialCollectionLatch, long theTime, TimeUnit theTimeUnit) {
|
||||
try {
|
||||
return theInitialCollectionLatch.await(theTime, theTimeUnit);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
ourLog.warn("Interrupted while waiting for latch");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,9 +20,8 @@ package ca.uhn.fhir.util;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.*;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.ICompositeType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -35,12 +34,10 @@ public class AttachmentUtil {
|
|||
* Fetches the base64Binary value of Attachment.data, creating it if it does not
|
||||
* already exist.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static IPrimitiveType<byte[]> getOrCreateData(FhirContext theContext, ICompositeType theAttachment) {
|
||||
return getOrCreateChild(theContext, theAttachment, "data", "base64Binary");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static IPrimitiveType<String> getOrCreateContentType(FhirContext theContext, ICompositeType theAttachment) {
|
||||
return getOrCreateChild(theContext, theAttachment, "contentType", "string");
|
||||
}
|
||||
|
@ -64,6 +61,16 @@ public class AttachmentUtil {
|
|||
});
|
||||
}
|
||||
|
||||
public static void setUrl(FhirContext theContext, ICompositeType theAttachment, String theUrl) {
|
||||
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "url");
|
||||
assert entryChild != null : "Version " + theContext + " has no child " + "url";
|
||||
String typeName = "uri";
|
||||
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
|
||||
typeName = "url";
|
||||
}
|
||||
entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, typeName, theUrl));
|
||||
}
|
||||
|
||||
public static void setContentType(FhirContext theContext, ICompositeType theAttachment, String theContentType) {
|
||||
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "contentType");
|
||||
entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, "code", theContentType));
|
||||
|
@ -88,7 +95,9 @@ public class AttachmentUtil {
|
|||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> IPrimitiveType<T> newPrimitive(FhirContext theContext, String theType, T theValue) {
|
||||
IPrimitiveType<T> primitive = (IPrimitiveType<T>) theContext.getElementDefinition(theType).newInstance();
|
||||
BaseRuntimeElementDefinition<?> elementDefinition = theContext.getElementDefinition(theType);
|
||||
Validate.notNull(elementDefinition, "Unknown type %s for %s", theType, theContext);
|
||||
IPrimitiveType<T> primitive = (IPrimitiveType<T>) elementDefinition.newInstance();
|
||||
primitive.setValue(theValue);
|
||||
return primitive;
|
||||
}
|
||||
|
@ -100,4 +109,8 @@ public class AttachmentUtil {
|
|||
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theElement.getClass());
|
||||
return def.getChildByName(theName);
|
||||
}
|
||||
|
||||
public static ICompositeType newInstance(FhirContext theFhirCtx) {
|
||||
return (ICompositeType) theFhirCtx.getElementDefinition("Attachment").newInstance();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.util.bundle.BundleEntryMutator;
|
||||
import ca.uhn.fhir.util.bundle.BundleEntryParts;
|
||||
import ca.uhn.fhir.util.bundle.EntryListAccumulator;
|
||||
import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -253,4 +252,25 @@ public class BundleUtil {
|
|||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* DSTU3 did not allow the PATCH verb for transaction bundles- so instead we infer that a bundle
|
||||
* is a patch if the payload is a binary resource containing a patch. This method
|
||||
* tests whether a resource (which should have come from
|
||||
* <code>Bundle.entry.resource</code> is a Binary resource with a patch
|
||||
* payload type.
|
||||
*/
|
||||
public static boolean isDstu3TransactionPatch(IBaseResource thePayloadResource) {
|
||||
boolean isPatch = false;
|
||||
if (thePayloadResource instanceof IBaseBinary) {
|
||||
String contentType = ((IBaseBinary) thePayloadResource).getContentType();
|
||||
try {
|
||||
PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType);
|
||||
isPatch = true;
|
||||
} catch (InvalidRequestException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return isPatch;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 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.text.DecimalFormat;
|
||||
|
||||
public class FileUtil {
|
||||
// Use "bytes" instead of just "b" because it reads easier in logs
|
||||
private static final String[] UNITS = new String[]{"Bytes", "kB", "MB", "GB", "TB"};
|
||||
|
||||
public static String formatFileSize(long theBytes) {
|
||||
if (theBytes <= 0) {
|
||||
return "0 " + UNITS[0];
|
||||
}
|
||||
int digitGroups = (int) (Math.log10(theBytes) / Math.log10(1024));
|
||||
digitGroups = Math.min(digitGroups, UNITS.length - 1);
|
||||
return new DecimalFormat("###0.#").format(theBytes / Math.pow(1024, digitGroups)) + " " + UNITS[digitGroups];
|
||||
}
|
||||
|
||||
}
|
|
@ -45,7 +45,7 @@ public interface IModelVisitor2 {
|
|||
/**
|
||||
*
|
||||
*/
|
||||
boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath);
|
||||
default boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { return true; }
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ package ca.uhn.fhir.util;
|
|||
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.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -46,6 +46,29 @@ public class MetaUtil {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for <code>Resource.meta.source</code> for R4+ resources, and places the value in
|
||||
* an extension on <code>Resource.meta</code>
|
||||
* with the URL <code>http://hapifhir.io/fhir/StructureDefinition/resource-meta-source</code> for DSTU3.
|
||||
*
|
||||
* @param theContext The FhirContext object
|
||||
* @param theResource The resource to modify
|
||||
* @param theValue The source URI
|
||||
* @see <a href="http://hl7.org/fhir/resource-definitions.html#Resource.meta">Meta.source</a>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void setSource(FhirContext theContext, IBaseResource theResource, String theValue) {
|
||||
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
|
||||
MetaUtil.setSource(theContext, theResource.getMeta(), theValue);
|
||||
} else if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
|
||||
IBaseExtension<?, ?> sourceExtension = ((IBaseHasExtensions) theResource.getMeta()).addExtension();
|
||||
sourceExtension.setUrl(Constants.EXT_META_SOURCE);
|
||||
IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("uri").newInstance();
|
||||
value.setValue(theValue);
|
||||
sourceExtension.setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSource(FhirContext theContext, IBaseMetaType theMeta, String theValue) {
|
||||
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theMeta.getClass());
|
||||
BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");
|
||||
|
|
|
@ -19,22 +19,17 @@ package ca.uhn.fhir.util;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||
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 ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/**
|
||||
* Utilities for dealing with OperationOutcome resources across various model versions
|
||||
|
@ -43,20 +38,17 @@ public class OperationOutcomeUtil {
|
|||
|
||||
/**
|
||||
* Add an issue to an OperationOutcome
|
||||
*
|
||||
* @param theCtx
|
||||
* The fhir context
|
||||
* @param theOperationOutcome
|
||||
* The OO resource to add to
|
||||
* @param theSeverity
|
||||
* The severity (fatal | error | warning | information)
|
||||
* @param theDetails
|
||||
* The details string
|
||||
* @param theCtx The fhir context
|
||||
* @param theOperationOutcome The OO resource to add to
|
||||
* @param theSeverity The severity (fatal | error | warning | information)
|
||||
* @param theDetails The details string
|
||||
* @param theCode
|
||||
* @return Returns the newly added issue
|
||||
*/
|
||||
public static void addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) {
|
||||
public static IBase addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) {
|
||||
IBase issue = createIssue(theCtx, theOperationOutcome);
|
||||
populateDetails(theCtx, issue, theSeverity, theDetails, theLocation, theCode);
|
||||
return issue;
|
||||
}
|
||||
|
||||
private static IBase createIssue(FhirContext theCtx, IBaseResource theOutcome) {
|
||||
|
@ -140,7 +132,6 @@ public class OperationOutcomeUtil {
|
|||
|
||||
BaseRuntimeElementDefinition<?> stringDef = detailsChild.getChildByName(detailsChild.getElementName());
|
||||
BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity");
|
||||
BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location");
|
||||
|
||||
IPrimitiveType<?> severityElem = (IPrimitiveType<?>) severityChild.getChildByName("severity").newInstance(severityChild.getInstanceConstructorArguments());
|
||||
severityElem.setValueAsString(theSeverity);
|
||||
|
@ -150,7 +141,13 @@ public class OperationOutcomeUtil {
|
|||
string.setValueAsString(theDetails);
|
||||
detailsChild.getMutator().setValue(theIssue, string);
|
||||
|
||||
addLocationToIssue(theCtx, theIssue, theLocation);
|
||||
}
|
||||
|
||||
public static void addLocationToIssue(FhirContext theContext, IBase theIssue, String theLocation) {
|
||||
if (isNotBlank(theLocation)) {
|
||||
BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theIssue.getClass());
|
||||
BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location");
|
||||
IPrimitiveType<?> locationElem = (IPrimitiveType<?>) locationChild.getChildByName("location").newInstance(locationChild.getInstanceConstructorArguments());
|
||||
locationElem.setValueAsString(theLocation);
|
||||
locationChild.getMutator().addValue(theIssue, locationElem);
|
||||
|
|
|
@ -72,6 +72,8 @@ public class ParametersUtil {
|
|||
}
|
||||
|
||||
private static void addClientParameter(FhirContext theContext, Object theValue, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem, String theName) {
|
||||
Validate.notNull(theValue, "theValue must not be null");
|
||||
|
||||
if (theValue instanceof IBaseResource) {
|
||||
IBase parameter = createParameterRepetition(theContext, theTargetResource, paramChild, paramChildElem, theName);
|
||||
paramChildElem.getChildByName("resource").getMutator().addValue(parameter, (IBaseResource) theValue);
|
||||
|
@ -162,7 +164,6 @@ public class ParametersUtil {
|
|||
IPrimitiveType<Boolean> value = (IPrimitiveType<Boolean>) theCtx.getElementDefinition("boolean").newInstance();
|
||||
value.setValue(theValue);
|
||||
addParameterToParameters(theCtx, theParameters, theName, value);
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -51,16 +51,8 @@ public class ReflectionUtil {
|
|||
}
|
||||
|
||||
public static Class<?> getGenericCollectionTypeOfField(Field next) {
|
||||
Class<?> type;
|
||||
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
|
||||
Type firstArg = collectionType.getActualTypeArguments()[0];
|
||||
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
|
||||
ParameterizedType pt = ((ParameterizedType) firstArg);
|
||||
type = (Class<?>) pt.getRawType();
|
||||
} else {
|
||||
type = (Class<?>) firstArg;
|
||||
}
|
||||
return type;
|
||||
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -84,42 +76,37 @@ public class ReflectionUtil {
|
|||
}
|
||||
|
||||
public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) {
|
||||
Class<?> type;
|
||||
Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex];
|
||||
if (Class.class.equals(genericParameterType) || Class.class.equals(genericParameterType.getClass())) {
|
||||
return null;
|
||||
}
|
||||
ParameterizedType collectionType = (ParameterizedType) genericParameterType;
|
||||
Type firstArg = collectionType.getActualTypeArguments()[0];
|
||||
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
|
||||
ParameterizedType pt = ((ParameterizedType) firstArg);
|
||||
type = (Class<?>) pt.getRawType();
|
||||
} else {
|
||||
type = (Class<?>) firstArg;
|
||||
}
|
||||
return type;
|
||||
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
public static Class<?> getGenericCollectionTypeOfMethodReturnType(Method theMethod) {
|
||||
Class<?> type;
|
||||
Type genericReturnType = theMethod.getGenericReturnType();
|
||||
if (!(genericReturnType instanceof ParameterizedType)) {
|
||||
return null;
|
||||
}
|
||||
ParameterizedType collectionType = (ParameterizedType) genericReturnType;
|
||||
Type firstArg = collectionType.getActualTypeArguments()[0];
|
||||
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
|
||||
ParameterizedType pt = ((ParameterizedType) firstArg);
|
||||
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
private static Class<?> getGenericCollectionTypeOf(Type theType) {
|
||||
Class<?> type;
|
||||
if (ParameterizedType.class.isAssignableFrom(theType.getClass())) {
|
||||
ParameterizedType pt = ((ParameterizedType) theType);
|
||||
type = (Class<?>) pt.getRawType();
|
||||
} else if (firstArg instanceof TypeVariable<?>) {
|
||||
Type decl = ((TypeVariable) firstArg).getBounds()[0];
|
||||
} else if (theType instanceof TypeVariable<?>) {
|
||||
Type decl = ((TypeVariable) theType).getBounds()[0];
|
||||
return (Class<?>) decl;
|
||||
} else if (firstArg instanceof WildcardType) {
|
||||
Type decl = ((WildcardType) firstArg).getUpperBounds()[0];
|
||||
} else if (theType instanceof WildcardType) {
|
||||
Type decl = ((WildcardType) theType).getUpperBounds()[0];
|
||||
return (Class<?>) decl;
|
||||
} else {
|
||||
type = (Class<?>) firstArg;
|
||||
type = (Class<?>) theType;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
@ -154,8 +141,7 @@ public class ReflectionUtil {
|
|||
public static Object newInstanceOfFhirServerType(String theType) {
|
||||
String errorMessage = "Unable to instantiate server framework. Please make sure that hapi-fhir-server library is on your classpath!";
|
||||
String wantedType = "ca.uhn.fhir.rest.api.server.IFhirVersionServer";
|
||||
Object fhirServerVersion = newInstanceOfType(theType, errorMessage, wantedType);
|
||||
return fhirServerVersion;
|
||||
return newInstanceOfType(theType, errorMessage, wantedType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
@ -9,8 +10,6 @@ import java.util.Date;
|
|||
import java.util.LinkedList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
|
@ -48,12 +47,14 @@ public class StopWatch {
|
|||
private long myStarted = now();
|
||||
private TaskTiming myCurrentTask;
|
||||
private LinkedList<TaskTiming> myTasks;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public StopWatch() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -63,7 +64,13 @@ public class StopWatch {
|
|||
myStarted = theStart.getTime();
|
||||
}
|
||||
|
||||
public StopWatch(long theL) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theStart The time that the stopwatch was started
|
||||
*/
|
||||
public StopWatch(long theStart) {
|
||||
myStarted = theStart;
|
||||
}
|
||||
|
||||
private void addNewlineIfContentExists(StringBuilder theB) {
|
||||
|
@ -120,6 +127,8 @@ public class StopWatch {
|
|||
b.append(": ");
|
||||
b.append(formatMillis(delta));
|
||||
}
|
||||
} else {
|
||||
b.append("No tasks");
|
||||
}
|
||||
|
||||
TaskTiming last = null;
|
||||
|
@ -257,12 +266,11 @@ public class StopWatch {
|
|||
*/
|
||||
public void startTask(String theTaskName) {
|
||||
endCurrentTask();
|
||||
if (isNotBlank(theTaskName)) {
|
||||
myCurrentTask = new TaskTiming()
|
||||
.setTaskName(theTaskName)
|
||||
.setStart(now());
|
||||
myTasks.add(myCurrentTask);
|
||||
}
|
||||
Validate.notBlank(theTaskName, "Task name must not be blank");
|
||||
myCurrentTask = new TaskTiming()
|
||||
.setTaskName(theTaskName)
|
||||
.setStart(now());
|
||||
myTasks.add(myCurrentTask);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -331,18 +339,18 @@ public class StopWatch {
|
|||
/**
|
||||
* Append a right-aligned and zero-padded numeric value to a `StringBuilder`.
|
||||
*/
|
||||
static private void append(StringBuilder tgt, String pfx, int dgt, long val) {
|
||||
tgt.append(pfx);
|
||||
if (dgt > 1) {
|
||||
int pad = (dgt - 1);
|
||||
for (long xa = val; xa > 9 && pad > 0; xa /= 10) {
|
||||
static void appendRightAlignedNumber(StringBuilder theStringBuilder, String thePrefix, int theNumberOfDigits, long theValueToAppend) {
|
||||
theStringBuilder.append(thePrefix);
|
||||
if (theNumberOfDigits > 1) {
|
||||
int pad = (theNumberOfDigits - 1);
|
||||
for (long xa = theValueToAppend; xa > 9 && pad > 0; xa /= 10) {
|
||||
pad--;
|
||||
}
|
||||
for (int xa = 0; xa < pad; xa++) {
|
||||
tgt.append('0');
|
||||
theStringBuilder.append('0');
|
||||
}
|
||||
}
|
||||
tgt.append(val);
|
||||
theStringBuilder.append(theValueToAppend);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -399,11 +407,11 @@ public class StopWatch {
|
|||
}
|
||||
} else {
|
||||
long millisAsLong = (long) theMillis;
|
||||
append(buf, "", 2, ((millisAsLong % DateUtils.MILLIS_PER_DAY) / DateUtils.MILLIS_PER_HOUR));
|
||||
append(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_HOUR) / DateUtils.MILLIS_PER_MINUTE));
|
||||
append(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_MINUTE) / DateUtils.MILLIS_PER_SECOND));
|
||||
appendRightAlignedNumber(buf, "", 2, ((millisAsLong % DateUtils.MILLIS_PER_DAY) / DateUtils.MILLIS_PER_HOUR));
|
||||
appendRightAlignedNumber(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_HOUR) / DateUtils.MILLIS_PER_MINUTE));
|
||||
appendRightAlignedNumber(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_MINUTE) / DateUtils.MILLIS_PER_SECOND));
|
||||
if (theMillis <= DateUtils.MILLIS_PER_MINUTE) {
|
||||
append(buf, ".", 3, (millisAsLong % DateUtils.MILLIS_PER_SECOND));
|
||||
appendRightAlignedNumber(buf, ".", 3, (millisAsLong % DateUtils.MILLIS_PER_SECOND));
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
|
|
|
@ -30,7 +30,13 @@ import com.ctc.wstx.stax.WstxOutputFactory;
|
|||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.codehaus.stax2.XMLOutputFactory2;
|
||||
import org.codehaus.stax2.io.EscapingWriterFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.stream.*;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
import java.io.*;
|
||||
|
@ -40,7 +46,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
|
||||
/**
|
||||
* Utility methods for working with the StAX API.
|
||||
*
|
||||
* <p>
|
||||
* This class contains code adapted from the Apache Axiom project.
|
||||
*/
|
||||
public class XmlUtil {
|
||||
|
@ -1503,10 +1509,77 @@ public class XmlUtil {
|
|||
validEntityNames.put("zscr", 0x1D4CF);
|
||||
validEntityNames.put("zwj", 0x0200D);
|
||||
validEntityNames.put("zwnj", 0x0200C);
|
||||
|
||||
|
||||
VALID_ENTITY_NAMES = Collections.unmodifiableMap(validEntityNames);
|
||||
}
|
||||
|
||||
/** Non-instantiable */
|
||||
private XmlUtil() {}
|
||||
|
||||
private static final class ExtendedEntityReplacingXmlResolver implements XMLResolver {
|
||||
@Override
|
||||
public Object resolveEntity(String thePublicID, String theSystemID, String theBaseURI, String theNamespace) {
|
||||
if (thePublicID == null && theSystemID == null) {
|
||||
if (theNamespace != null && VALID_ENTITY_NAMES.containsKey(theNamespace)) {
|
||||
return new String(Character.toChars(VALID_ENTITY_NAMES.get(theNamespace)));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyEscaper implements EscapingWriterFactory {
|
||||
|
||||
@Override
|
||||
public Writer createEscapingWriterFor(OutputStream theOut, String theEnc) throws UnsupportedEncodingException {
|
||||
return createEscapingWriterFor(new OutputStreamWriter(theOut, theEnc), theEnc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer createEscapingWriterFor(final Writer theW, String theEnc) {
|
||||
return new Writer() {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
theW.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
theW.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char[] theCbuf, int theOff, int theLen) throws IOException {
|
||||
boolean hasEscapable = false;
|
||||
for (int i = 0; i < theLen && !hasEscapable; i++) {
|
||||
char nextChar = theCbuf[i + theOff];
|
||||
switch (nextChar) {
|
||||
case '<':
|
||||
case '>':
|
||||
case '"':
|
||||
case '&':
|
||||
hasEscapable = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasEscapable) {
|
||||
theW.write(theCbuf, theOff, theLen);
|
||||
return;
|
||||
}
|
||||
|
||||
String escaped = StringEscapeUtils.escapeXml10(new String(theCbuf, theOff, theLen));
|
||||
theW.write(escaped.toCharArray());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static XMLOutputFactory createOutputFactory() throws FactoryConfigurationError {
|
||||
try {
|
||||
// Detect if we're running with the Android lib, and force repackaged Woodstox to be used
|
||||
|
@ -1637,15 +1710,11 @@ public class XmlUtil {
|
|||
try {
|
||||
Class.forName("com.ctc.wstx.stax.WstxInputFactory");
|
||||
boolean isWoodstox = inputFactory instanceof com.ctc.wstx.stax.WstxInputFactory;
|
||||
if ( !isWoodstox )
|
||||
{
|
||||
if (!isWoodstox) {
|
||||
// Check if implementation is woodstox by property since instanceof check does not work if running in JBoss
|
||||
try
|
||||
{
|
||||
isWoodstox = inputFactory.getProperty( "org.codehaus.stax2.implVersion" ) != null;
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
try {
|
||||
isWoodstox = inputFactory.getProperty("org.codehaus.stax2.implVersion") != null;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
@ -1673,7 +1742,6 @@ public class XmlUtil {
|
|||
return ourOutputFactory;
|
||||
}
|
||||
|
||||
|
||||
private static void logStaxImplementation(Class<?> theClass) {
|
||||
IDependencyLog logger = DependencyLogFactory.createJarLogger();
|
||||
if (logger != null) {
|
||||
|
@ -1682,7 +1750,6 @@ public class XmlUtil {
|
|||
ourHaveLoggedStaxImplementation = true;
|
||||
}
|
||||
|
||||
|
||||
static XMLInputFactory newInputFactory() throws FactoryConfigurationError {
|
||||
XMLInputFactory inputFactory;
|
||||
try {
|
||||
|
@ -1761,74 +1828,36 @@ public class XmlUtil {
|
|||
private static void throwUnitTestExceptionIfConfiguredToDoSo() throws FactoryConfigurationError, XMLStreamException {
|
||||
if (ourNextException != null) {
|
||||
if (ourNextException instanceof javax.xml.stream.FactoryConfigurationError) {
|
||||
throw ((javax.xml.stream.FactoryConfigurationError)ourNextException);
|
||||
throw ((javax.xml.stream.FactoryConfigurationError) ourNextException);
|
||||
}
|
||||
throw (XMLStreamException)ourNextException;
|
||||
throw (XMLStreamException) ourNextException;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ExtendedEntityReplacingXmlResolver implements XMLResolver {
|
||||
@Override
|
||||
public Object resolveEntity(String thePublicID, String theSystemID, String theBaseURI, String theNamespace) {
|
||||
if (thePublicID == null && theSystemID == null) {
|
||||
if (theNamespace != null && VALID_ENTITY_NAMES.containsKey(theNamespace)) {
|
||||
return new String(Character.toChars(VALID_ENTITY_NAMES.get(theNamespace)));
|
||||
}
|
||||
public static Document parseDocument(String theInput) throws IOException, SAXException {
|
||||
DocumentBuilder builder = null;
|
||||
try {
|
||||
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
docBuilderFactory.setNamespaceAware(true);
|
||||
docBuilderFactory.setXIncludeAware(false);
|
||||
docBuilderFactory.setExpandEntityReferences(false);
|
||||
try {
|
||||
docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
docBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
|
||||
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
docBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
throwUnitTestExceptionIfConfiguredToDoSo();
|
||||
} catch (Exception e) {
|
||||
ourLog.warn("Failed to set feature on XML parser: " + e.toString());
|
||||
}
|
||||
|
||||
return null;
|
||||
builder = docBuilderFactory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
|
||||
InputSource src = new InputSource(new StringReader(theInput));
|
||||
return builder.parse(src);
|
||||
}
|
||||
|
||||
public static class MyEscaper implements EscapingWriterFactory {
|
||||
|
||||
@Override
|
||||
public Writer createEscapingWriterFor(OutputStream theOut, String theEnc) throws UnsupportedEncodingException {
|
||||
return createEscapingWriterFor(new OutputStreamWriter(theOut, theEnc), theEnc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer createEscapingWriterFor(final Writer theW, String theEnc) {
|
||||
return new Writer() {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
theW.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
theW.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char[] theCbuf, int theOff, int theLen) throws IOException {
|
||||
boolean hasEscapable = false;
|
||||
for (int i = 0; i < theLen && !hasEscapable; i++) {
|
||||
char nextChar = theCbuf[i + theOff];
|
||||
switch (nextChar) {
|
||||
case '<':
|
||||
case '>':
|
||||
case '"':
|
||||
case '&':
|
||||
hasEscapable = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasEscapable) {
|
||||
theW.write(theCbuf, theOff, theLen);
|
||||
return;
|
||||
}
|
||||
|
||||
String escaped = StringEscapeUtils.escapeXml10(new String(theCbuf, theOff, theLen));
|
||||
theW.write(escaped.toCharArray());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package ca.uhn.fhir.validation;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 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%
|
||||
*/
|
||||
|
||||
/**
|
||||
* This interface marks a {@link IValidatorModule validator module} that uses the FHIR
|
||||
* FhirInstanceValidator as the underlying engine (i.e. it performs
|
||||
* profile validation)
|
||||
*/
|
||||
public interface IInstanceValidatorModule extends IValidatorModule {
|
||||
|
||||
// nothing extra yet
|
||||
|
||||
}
|
|
@ -19,26 +19,32 @@ package ca.uhn.fhir.validation;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import javax.xml.validation.*;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.BOMInputStream;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.w3c.dom.ls.LSInput;
|
||||
import org.w3c.dom.ls.LSResourceResolver;
|
||||
import org.xml.sax.*;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.BOMInputStream;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.w3c.dom.ls.LSInput;
|
||||
import org.w3c.dom.ls.LSResourceResolver;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXNotRecognizedException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import javax.xml.validation.Schema;
|
||||
import javax.xml.validation.SchemaFactory;
|
||||
import javax.xml.validation.Validator;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
public class SchemaBaseValidator implements IValidatorModule {
|
||||
public static final String RESOURCES_JAR_NOTE = "Note that as of HAPI FHIR 1.2, DSTU2 validation files are kept in a separate JAR (hapi-fhir-validation-resources-XXX.jar) which must be added to your classpath. See the HAPI FHIR download page for more information.";
|
||||
|
@ -47,7 +53,7 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
private static final Set<String> SCHEMA_NAMES;
|
||||
|
||||
static {
|
||||
HashSet<String> sn = new HashSet<String>();
|
||||
HashSet<String> sn = new HashSet<>();
|
||||
sn.add("xml.xsd");
|
||||
sn.add("xhtml1-strict.xsd");
|
||||
sn.add("fhir-single.xsd");
|
||||
|
@ -59,15 +65,15 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
SCHEMA_NAMES = Collections.unmodifiableSet(sn);
|
||||
}
|
||||
|
||||
private Map<String, Schema> myKeyToSchema = new HashMap<String, Schema>();
|
||||
private final Map<String, Schema> myKeyToSchema = new HashMap<>();
|
||||
private FhirContext myCtx;
|
||||
|
||||
public SchemaBaseValidator(FhirContext theContext) {
|
||||
myCtx = theContext;
|
||||
}
|
||||
|
||||
private void doValidate(IValidationContext<?> theContext, String schemaName) {
|
||||
Schema schema = loadSchema("dstu", schemaName);
|
||||
private void doValidate(IValidationContext<?> theContext) {
|
||||
Schema schema = loadSchema();
|
||||
|
||||
try {
|
||||
Validator validator = schema.newValidator();
|
||||
|
@ -81,14 +87,14 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/339
|
||||
* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
|
||||
*/
|
||||
/*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/339
|
||||
* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
|
||||
*/
|
||||
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
||||
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
|
||||
}catch (SAXNotRecognizedException ex){
|
||||
ourLog.warn("Jaxp 1.5 Support not found.",ex);
|
||||
} catch (SAXNotRecognizedException ex) {
|
||||
ourLog.warn("Jaxp 1.5 Support not found.", ex);
|
||||
}
|
||||
|
||||
validator.validate(new StreamSource(new StringReader(encodedResource)));
|
||||
|
@ -99,17 +105,14 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
message.setMessage(e.getLocalizedMessage());
|
||||
message.setSeverity(ResultSeverityEnum.FATAL);
|
||||
theContext.addValidationMessage(message);
|
||||
} catch (SAXException e) {
|
||||
// Catch all
|
||||
throw new ConfigurationException("Could not load/parse schema file", e);
|
||||
} catch (IOException e) {
|
||||
} catch (SAXException | IOException e) {
|
||||
// Catch all
|
||||
throw new ConfigurationException("Could not load/parse schema file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Schema loadSchema(String theVersion, String theSchemaName) {
|
||||
String key = theVersion + "-" + theSchemaName;
|
||||
private Schema loadSchema() {
|
||||
String key = "fhir-single.xsd";
|
||||
|
||||
synchronized (myKeyToSchema) {
|
||||
Schema schema = myKeyToSchema.get(key);
|
||||
|
@ -117,81 +120,52 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
return schema;
|
||||
}
|
||||
|
||||
Source baseSource = loadXml(null, theSchemaName);
|
||||
Source baseSource = loadXml("fhir-single.xsd");
|
||||
|
||||
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
||||
schemaFactory.setResourceResolver(new MyResourceResolver());
|
||||
|
||||
try {
|
||||
try {
|
||||
/*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/339
|
||||
* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
|
||||
*/
|
||||
/*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/339
|
||||
* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
|
||||
*/
|
||||
schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
||||
}catch (SAXNotRecognizedException snex){
|
||||
ourLog.warn("Jaxp 1.5 Support not found.",snex);
|
||||
} catch (SAXNotRecognizedException e) {
|
||||
ourLog.warn("Jaxp 1.5 Support not found.", e);
|
||||
}
|
||||
schema = schemaFactory.newSchema(new Source[] { baseSource });
|
||||
schema = schemaFactory.newSchema(new Source[]{baseSource});
|
||||
} catch (SAXException e) {
|
||||
throw new ConfigurationException("Could not load/parse schema file: " + theSchemaName, e);
|
||||
throw new ConfigurationException("Could not load/parse schema file: " + "fhir-single.xsd", e);
|
||||
}
|
||||
myKeyToSchema.put(key, schema);
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
|
||||
private Source loadXml(String theSystemId, String theSchemaName) {
|
||||
Source loadXml(String theSchemaName) {
|
||||
String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theSchemaName;
|
||||
ourLog.debug("Going to load resource: {}", pathToBase);
|
||||
InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase);
|
||||
if (baseIs == null) {
|
||||
throw new InternalErrorException("Schema not found. " + RESOURCES_JAR_NOTE);
|
||||
try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) {
|
||||
if (baseIs == null) {
|
||||
throw new InternalErrorException("Schema not found. " + RESOURCES_JAR_NOTE);
|
||||
}
|
||||
try (BOMInputStream bomInputStream = new BOMInputStream(baseIs, false)) {
|
||||
try (InputStreamReader baseReader = new InputStreamReader(bomInputStream, StandardCharsets.UTF_8)) {
|
||||
// Buffer so that we can close the input stream
|
||||
String contents = IOUtils.toString(baseReader);
|
||||
return new StreamSource(new StringReader(contents), null);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
baseIs = new BOMInputStream(baseIs, false);
|
||||
InputStreamReader baseReader = new InputStreamReader(baseIs, Charset.forName("UTF-8"));
|
||||
Source baseSource = new StreamSource(baseReader, theSystemId);
|
||||
//FIXME resource leak
|
||||
return baseSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateResource(IValidationContext<IBaseResource> theContext) {
|
||||
doValidate(theContext, "fhir-single.xsd");
|
||||
}
|
||||
|
||||
private static class MyErrorHandler implements org.xml.sax.ErrorHandler {
|
||||
|
||||
private IValidationContext<?> myContext;
|
||||
|
||||
public MyErrorHandler(IValidationContext<?> theContext) {
|
||||
myContext = theContext;
|
||||
}
|
||||
|
||||
private void addIssue(SAXParseException theException, ResultSeverityEnum theSeverity) {
|
||||
SingleValidationMessage message = new SingleValidationMessage();
|
||||
message.setLocationLine(theException.getLineNumber());
|
||||
message.setLocationCol(theException.getColumnNumber());
|
||||
message.setMessage(theException.getLocalizedMessage());
|
||||
message.setSeverity(theSeverity);
|
||||
myContext.addValidationMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.FATAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.WARNING);
|
||||
}
|
||||
|
||||
doValidate(theContext);
|
||||
}
|
||||
|
||||
private final class MyResourceResolver implements LSResourceResolver {
|
||||
|
@ -225,4 +199,38 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
}
|
||||
}
|
||||
|
||||
private static class MyErrorHandler implements org.xml.sax.ErrorHandler {
|
||||
|
||||
private IValidationContext<?> myContext;
|
||||
|
||||
MyErrorHandler(IValidationContext<?> theContext) {
|
||||
myContext = theContext;
|
||||
}
|
||||
|
||||
private void addIssue(SAXParseException theException, ResultSeverityEnum theSeverity) {
|
||||
SingleValidationMessage message = new SingleValidationMessage();
|
||||
message.setLocationLine(theException.getLineNumber());
|
||||
message.setLocationCol(theException.getColumnNumber());
|
||||
message.setMessage(theException.getLocalizedMessage());
|
||||
message.setSeverity(theSeverity);
|
||||
myContext.addValidationMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.FATAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.WARNING);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -120,7 +121,19 @@ public class ValidationResult {
|
|||
location = null;
|
||||
}
|
||||
String severity = next.getSeverity() != null ? next.getSeverity().getCode() : null;
|
||||
OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, severity, next.getMessage(), location, Constants.OO_INFOSTATUS_PROCESSING);
|
||||
IBase issue = OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, severity, next.getMessage(), location, Constants.OO_INFOSTATUS_PROCESSING);
|
||||
|
||||
if (next.getLocationLine() != null || next.getLocationCol() != null) {
|
||||
String line = "(unknown)";
|
||||
if (next.getLocationLine() != null) {
|
||||
line = next.getLocationLine().toString();
|
||||
}
|
||||
String col = "(unknown)";
|
||||
if (next.getLocationCol() != null) {
|
||||
col = next.getLocationCol().toString();
|
||||
}
|
||||
OperationOutcomeUtil.addLocationToIssue(myCtx, issue, "Line " + line + ", Col " + col);
|
||||
}
|
||||
}
|
||||
|
||||
if (myMessages.isEmpty()) {
|
||||
|
|
|
@ -50,14 +50,12 @@ public interface IAnyResource extends IBaseResource {
|
|||
|
||||
String getId();
|
||||
|
||||
@Override
|
||||
IIdType getIdElement();
|
||||
|
||||
IPrimitiveType<String> getLanguageElement();
|
||||
|
||||
Object getUserData(String name);
|
||||
|
||||
@Override
|
||||
IAnyResource setId(String theId);
|
||||
|
||||
void setUserData(String name, Object value);
|
||||
|
|
|
@ -25,6 +25,8 @@ public interface IBaseBinary extends IBaseResource {
|
|||
|
||||
byte[] getContent();
|
||||
|
||||
IPrimitiveType<byte[]> getContentElement();
|
||||
|
||||
String getContentAsBase64();
|
||||
|
||||
String getContentType();
|
||||
|
|
|
@ -66,7 +66,7 @@ ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceVersionConstraintFail
|
|||
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceIndexedCompositeStringUniqueConstraintFailure=The operation has failed with a unique index constraint failure. This probably means that the operation was trying to create/update a resource that would have resulted in a duplicate value for a unique index.
|
||||
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.forcedIdConstraintFailure=The operation has failed with a client-assigned ID constraint failure. This typically means that multiple client threads are trying to create a new resource with the same client-assigned ID at the same time, and this thread was chosen to be rejected.
|
||||
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.externalizedBinaryStorageExtensionFoundInRequestBody=Illegal extension found in request payload: {0}
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.externalizedBinaryStorageExtensionFoundInRequestBody=Illegal extension found in request payload - URL "{0}" and value "{1}"
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlInvalidResourceType=Invalid match URL "{0}" - Unknown resource type: "{1}"
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlNoMatches=Invalid match URL "{0}" - No resources match this search
|
||||
|
@ -125,9 +125,9 @@ ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownPath=Unable to find content
|
|||
ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownType=Content in resource of type {0} at path {1} is not appropriate for binary storage: {2}
|
||||
|
||||
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
|
||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
|
||||
|
||||
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package ca.uhn.fhir.model.api;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ResourceMetadataKeyEnumTest {
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
assertEquals(-60968467, ResourceMetadataKeyEnum.PUBLISHED.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertNotEquals(ResourceMetadataKeyEnum.PROFILES, null);
|
||||
assertNotEquals(ResourceMetadataKeyEnum.PROFILES, "");
|
||||
assertNotEquals(ResourceMetadataKeyEnum.PROFILES, ResourceMetadataKeyEnum.PUBLISHED);
|
||||
assertEquals(ResourceMetadataKeyEnum.PROFILES, ResourceMetadataKeyEnum.PROFILES);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExtensionResourceEquals() {
|
||||
assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://bar"));
|
||||
assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), null);
|
||||
assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), "");
|
||||
assertEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"));
|
||||
|
||||
ResourceMetadataKeyEnum.ExtensionResourceMetadataKey foo = new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo");
|
||||
assertEquals(foo, foo);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package ca.uhn.fhir.model.api;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class TagTest {
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
Tag tag1 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
|
||||
Tag tag2 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
|
||||
Tag tag3 = new Tag().setScheme("scheme2").setTerm("term").setLabel("label");
|
||||
Tag tag4 = new Tag().setScheme("scheme").setTerm("term2").setLabel("label");
|
||||
|
||||
assertEquals(tag1, tag1);
|
||||
assertEquals(tag1, tag2);
|
||||
assertNotEquals(tag1, tag3);
|
||||
assertNotEquals(tag1, tag4);
|
||||
assertNotEquals(tag1, null);
|
||||
assertNotEquals(tag1, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
Tag tag1 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
|
||||
assertEquals(1920714536, tag1.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructors() throws URISyntaxException {
|
||||
assertTrue(new Tag().isEmpty());
|
||||
assertFalse(new Tag("http://foo").isEmpty());
|
||||
assertFalse(new Tag("http://foo", "http://bar").isEmpty());
|
||||
assertFalse(new Tag(new URI("http://foo"), new URI("http://bar"), "Label").isEmpty());
|
||||
assertTrue(new Tag((URI)null, null, "Label").isEmpty());
|
||||
|
||||
assertEquals("http://foo", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getSystem());
|
||||
assertEquals("http://bar", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getCode());
|
||||
assertEquals("Label", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getDisplay());
|
||||
}
|
||||
|
||||
}
|
|
@ -2,13 +2,34 @@ package ca.uhn.fhir.rest.param;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class TokenParamTest {
|
||||
@Test
|
||||
public void testEquals() {
|
||||
TokenParam tokenParam1 = new TokenParam("foo", "bar");
|
||||
TokenParam tokenParam2 = new TokenParam("foo", "bar");
|
||||
TokenParam tokenParam3 = new TokenParam("foo", "baz");
|
||||
assertEquals(tokenParam1, tokenParam1);
|
||||
assertEquals(tokenParam1, tokenParam2);
|
||||
assertNotEquals(tokenParam1, tokenParam3);
|
||||
assertNotEquals(tokenParam1, null);
|
||||
assertNotEquals(tokenParam1, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
TokenParam tokenParam1 = new TokenParam("foo", "bar");
|
||||
assertEquals(4716638, tokenParam1.hashCode());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIsEmpty() {
|
||||
assertFalse(new TokenParam("foo", "bar").isEmpty());
|
||||
assertTrue(new TokenParam("", "").isEmpty());
|
||||
assertTrue(new TokenParam().isEmpty());
|
||||
assertEquals("", new TokenParam().getValueNotNull());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
public class AsyncUtilTest {
|
||||
|
||||
@Test
|
||||
public void testSleep() {
|
||||
AsyncUtil.sleep(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSleepWithInterrupt() {
|
||||
AtomicBoolean outcomeHolder = new AtomicBoolean(true);
|
||||
Thread thread = new Thread(() -> {
|
||||
boolean outcome = AsyncUtil.sleep(10000);
|
||||
outcomeHolder.set(outcome);
|
||||
});
|
||||
thread.start();
|
||||
thread.interrupt();
|
||||
await().until(()-> outcomeHolder.get() == false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAwaitLatchAndThrowInternalErrorException() {
|
||||
AtomicBoolean outcomeHolder = new AtomicBoolean(false);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
AsyncUtil.awaitLatchAndThrowInternalErrorExceptionOnInterrupt(latch, 10, TimeUnit.SECONDS);
|
||||
} catch (InternalErrorException e) {
|
||||
outcomeHolder.set(true);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
thread.interrupt();
|
||||
await().until(()-> outcomeHolder.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAwaitLatchIgnoreInterruption() {
|
||||
AtomicBoolean outcomeHolder = new AtomicBoolean(true);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Thread thread = new Thread(() -> {
|
||||
boolean outcome = AsyncUtil.awaitLatchAndIgnoreInterrupt(latch, 10, TimeUnit.SECONDS);
|
||||
outcomeHolder.set(outcome);
|
||||
});
|
||||
thread.start();
|
||||
thread.interrupt();
|
||||
await().until(()-> outcomeHolder.get() == false);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class FileUtilTest {
|
||||
|
||||
@Test
|
||||
public void formatFileSize() {
|
||||
assertEquals("0 Bytes", FileUtil.formatFileSize(0).replace(",", "."));
|
||||
assertEquals("1 Bytes", FileUtil.formatFileSize(1).replace(",", "."));
|
||||
assertEquals("1.2 kB", FileUtil.formatFileSize(1234).replace(",", "."));
|
||||
assertEquals("12.1 kB", FileUtil.formatFileSize(12345).replace(",", "."));
|
||||
assertEquals("11.8 MB", FileUtil.formatFileSize(12345678).replace(",", "."));
|
||||
assertEquals("103.5 GB", FileUtil.formatFileSize(111111111111L).replace(",", "."));
|
||||
assertEquals("101.1 TB", FileUtil.formatFileSize(111111111111111L).replace(",", "."));
|
||||
assertEquals("10105.5 TB", FileUtil.formatFileSize(11111111111111111L).replace(",", "."));
|
||||
}
|
||||
}
|
|
@ -128,6 +128,56 @@ public class StopWatchTest {
|
|||
assertEquals("TASK1: 500ms\nTASK2: 100ms", taskDurations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatTaskDurationsDelayBetweenTasks() {
|
||||
StopWatch sw = new StopWatch();
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1000L);
|
||||
sw.startTask("TASK1");
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1500L);
|
||||
sw.endCurrentTask();
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(2000L);
|
||||
sw.startTask("TASK2");
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(2100L);
|
||||
sw.endCurrentTask();
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(2200L);
|
||||
String taskDurations = sw.formatTaskDurations();
|
||||
ourLog.info(taskDurations);
|
||||
assertEquals("TASK1: 500ms\n" +
|
||||
"Between: 500ms\n" +
|
||||
"TASK2: 100ms\n" +
|
||||
"After last task: 100ms", taskDurations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatTaskDurationsLongDelayBeforeStart() {
|
||||
StopWatch sw = new StopWatch(0);
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1000L);
|
||||
sw.startTask("TASK1");
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1500L);
|
||||
sw.startTask("TASK2");
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1600L);
|
||||
String taskDurations = sw.formatTaskDurations();
|
||||
ourLog.info(taskDurations);
|
||||
assertEquals("Before first task: 1000ms\nTASK1: 500ms\nTASK2: 100ms", taskDurations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatTaskDurationsNoTasks() {
|
||||
StopWatch sw = new StopWatch(0);
|
||||
|
||||
String taskDurations = sw.formatTaskDurations();
|
||||
ourLog.info(taskDurations);
|
||||
assertEquals("No tasks", taskDurations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatThroughput60Ops4Min() {
|
||||
StopWatch sw = new StopWatch(DateUtils.addMinutes(new Date(), -4));
|
||||
|
@ -210,4 +260,34 @@ public class StopWatchTest {
|
|||
assertThat(string, matchesPattern("^[0-9]{3,4}ms$"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAppendRightAlignedNumber() {
|
||||
StringBuilder b= new StringBuilder();
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 0, 100);
|
||||
assertEquals("PFX100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 1, 100);
|
||||
assertEquals("PFX100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 2, 100);
|
||||
assertEquals("PFX100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 3, 100);
|
||||
assertEquals("PFX100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 4, 100);
|
||||
assertEquals("PFX0100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 10, 100);
|
||||
assertEquals("PFX0000000100", b.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -252,6 +252,14 @@
|
|||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test Deps -->
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -398,7 +398,7 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
|
|||
}
|
||||
|
||||
protected IGenericClient newClientWithBaseUrl(CommandLine theCommandLine, String theBaseUrl, String theBasicAuthOptionName, String theBearerTokenOptionName) throws ParseException {
|
||||
myFhirCtx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000);
|
||||
myFhirCtx.getRestfulClientFactory().setSocketTimeout((int) DateUtils.MILLIS_PER_HOUR);
|
||||
IGenericClient retVal = myFhirCtx.newRestfulGenericClient(theBaseUrl);
|
||||
|
||||
String basicAuthHeaderValue = getAndParseOptionBasicAuthHeader(theCommandLine, theBasicAuthOptionName);
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.hl7.fhir.instance.model.api.IBaseBundle;
|
|||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
@ -79,7 +80,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
private Bundle getBundleFromFileDstu2(Integer limit, File inputFile, FhirContext ctx) throws IOException, UnsupportedEncodingException {
|
||||
private Bundle getBundleFromFileDstu2(Integer limit, File inputFile, FhirContext ctx) throws IOException {
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
|
@ -98,13 +99,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
break;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
int len;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
bos.write(buffer, 0, len);
|
||||
}
|
||||
byte[] exampleBytes = bos.toByteArray();
|
||||
String exampleString = new String(exampleBytes, "UTF-8");
|
||||
String exampleString = new String(exampleBytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (ourLog.isTraceEnabled()) {
|
||||
ourLog.trace("Next example: " + exampleString);
|
||||
|
@ -145,7 +146,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private org.hl7.fhir.dstu3.model.Bundle getBundleFromFileDstu3(Integer limit, File inputFile, FhirContext ctx) throws IOException, UnsupportedEncodingException {
|
||||
private org.hl7.fhir.dstu3.model.Bundle getBundleFromFileDstu3(Integer limit, File inputFile, FhirContext ctx) throws IOException {
|
||||
|
||||
org.hl7.fhir.dstu3.model.Bundle bundle = new org.hl7.fhir.dstu3.model.Bundle();
|
||||
bundle.setType(BundleType.TRANSACTION);
|
||||
|
@ -168,13 +169,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
break;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
int len;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
bos.write(buffer, 0, len);
|
||||
}
|
||||
byte[] exampleBytes = bos.toByteArray();
|
||||
String exampleString = new String(exampleBytes, "UTF-8");
|
||||
String exampleString = new String(exampleBytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (ourLog.isTraceEnabled()) {
|
||||
ourLog.trace("Next example: " + exampleString);
|
||||
|
@ -229,7 +230,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private org.hl7.fhir.r4.model.Bundle getBundleFromFileR4(Integer limit, File inputFile, FhirContext ctx) throws IOException, UnsupportedEncodingException {
|
||||
private org.hl7.fhir.r4.model.Bundle getBundleFromFileR4(Integer limit, File inputFile, FhirContext ctx) throws IOException {
|
||||
|
||||
org.hl7.fhir.r4.model.Bundle bundle = new org.hl7.fhir.r4.model.Bundle();
|
||||
bundle.setType(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION);
|
||||
|
@ -252,13 +253,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
break;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
int len;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
bos.write(buffer, 0, len);
|
||||
}
|
||||
byte[] exampleBytes = bos.toByteArray();
|
||||
String exampleString = new String(exampleBytes, "UTF-8");
|
||||
String exampleString = new String(exampleBytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (ourLog.isTraceEnabled()) {
|
||||
ourLog.trace("Next example: " + exampleString);
|
||||
|
@ -369,8 +370,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
|
||||
private void processBundleDstu2(FhirContext ctx, Bundle bundle) {
|
||||
|
||||
Map<String, Integer> ids = new HashMap<String, Integer>();
|
||||
Set<String> fullIds = new HashSet<String>();
|
||||
Set<String> fullIds = new HashSet<>();
|
||||
|
||||
for (Iterator<Entry> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
Entry next = iterator.next();
|
||||
|
@ -397,13 +397,14 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
Set<String> qualIds = new TreeSet<String>();
|
||||
for (Iterator<Entry> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
Entry next = iterator.next();
|
||||
Set<String> qualIds = new TreeSet<>();
|
||||
|
||||
for (Entry next : bundle.getEntry()) {
|
||||
if (next.getResource().getId().getIdPart() != null) {
|
||||
String nextId = next.getResource().getId().getValue();
|
||||
next.getRequest().setMethod(HTTPVerbEnum.PUT);
|
||||
next.getRequest().setUrl(nextId);
|
||||
qualIds.add(nextId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,15 +450,14 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
|
||||
private void processBundleDstu3(FhirContext ctx, org.hl7.fhir.dstu3.model.Bundle bundle) {
|
||||
|
||||
Map<String, Integer> ids = new HashMap<String, Integer>();
|
||||
Set<String> fullIds = new HashSet<String>();
|
||||
Set<String> fullIds = new HashSet<>();
|
||||
|
||||
for (Iterator<BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
BundleEntryComponent next = iterator.next();
|
||||
|
||||
// DataElement have giant IDs that seem invalid, need to investigate this..
|
||||
if ("Subscription".equals(next.getResource().getResourceType()) || "DataElement".equals(next.getResource().getResourceType())
|
||||
|| "OperationOutcome".equals(next.getResource().getResourceType()) || "OperationDefinition".equals(next.getResource().getResourceType())) {
|
||||
if ("Subscription".equals(next.getResource().getResourceType().name()) || "DataElement".equals(next.getResource().getResourceType().name())
|
||||
|| "OperationOutcome".equals(next.getResource().getResourceType().name()) || "OperationDefinition".equals(next.getResource().getResourceType().name())) {
|
||||
ourLog.info("Skipping " + next.getResource().getResourceType() + " example");
|
||||
iterator.remove();
|
||||
} else {
|
||||
|
@ -477,13 +477,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
Set<String> qualIds = new TreeSet<String>();
|
||||
for (Iterator<BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
BundleEntryComponent next = iterator.next();
|
||||
Set<String> qualIds = new TreeSet<>();
|
||||
for (BundleEntryComponent next : bundle.getEntry()) {
|
||||
if (next.getResource().getIdElement().getIdPart() != null) {
|
||||
String nextId = next.getResource().getIdElement().getValue();
|
||||
next.getRequest().setMethod(HTTPVerb.PUT);
|
||||
next.getRequest().setUrl(nextId);
|
||||
qualIds.add(nextId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -529,15 +529,14 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
|
||||
private void processBundleR4(FhirContext ctx, org.hl7.fhir.r4.model.Bundle bundle) {
|
||||
|
||||
Map<String, Integer> ids = new HashMap<String, Integer>();
|
||||
Set<String> fullIds = new HashSet<String>();
|
||||
Set<String> fullIds = new HashSet<>();
|
||||
|
||||
for (Iterator<org.hl7.fhir.r4.model.Bundle.BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next = iterator.next();
|
||||
|
||||
// DataElement have giant IDs that seem invalid, need to investigate this..
|
||||
if ("Subscription".equals(next.getResource().getResourceType()) || "DataElement".equals(next.getResource().getResourceType())
|
||||
|| "OperationOutcome".equals(next.getResource().getResourceType()) || "OperationDefinition".equals(next.getResource().getResourceType())) {
|
||||
if ("Subscription".equals(next.getResource().getResourceType().name()) || "DataElement".equals(next.getResource().getResourceType().name())
|
||||
|| "OperationOutcome".equals(next.getResource().getResourceType().name()) || "OperationDefinition".equals(next.getResource().getResourceType().name())) {
|
||||
ourLog.info("Skipping " + next.getResource().getResourceType() + " example");
|
||||
iterator.remove();
|
||||
} else {
|
||||
|
@ -557,13 +556,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
Set<String> qualIds = new TreeSet<String>();
|
||||
for (Iterator<org.hl7.fhir.r4.model.Bundle.BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next = iterator.next();
|
||||
Set<String> qualIds = new TreeSet<>();
|
||||
for (org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next : bundle.getEntry()) {
|
||||
if (next.getResource().getIdElement().getIdPart() != null) {
|
||||
String nextId = next.getResource().getIdElement().getValue();
|
||||
next.getRequest().setMethod(org.hl7.fhir.r4.model.Bundle.HTTPVerb.PUT);
|
||||
next.getRequest().setUrl(nextId);
|
||||
qualIds.add(nextId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,7 +634,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
|
||||
boolean cacheFile = theCommandLine.hasOption('c');
|
||||
|
||||
Collection<File> inputFiles = null;
|
||||
Collection<File> inputFiles;
|
||||
try {
|
||||
inputFiles = loadFile(specUrl, filepath, cacheFile);
|
||||
for (File inputFile : inputFiles) {
|
||||
|
@ -694,13 +693,11 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
continue;
|
||||
}
|
||||
|
||||
boolean found = false;
|
||||
for (int j = 0; j < resources.size(); j++) {
|
||||
String candidateTarget = resources.get(j).getIdElement().getValue();
|
||||
if (isNotBlank(nextTarget) && nextTarget.equals(candidateTarget)) {
|
||||
ourLog.info("Reflexively adding resource {} to bundle as it is a reference target", nextTarget);
|
||||
subResourceList.add(resources.remove(j));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,35 +123,34 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept
|
|||
private void convertConceptMapToCsv(ConceptMap theConceptMap) {
|
||||
Path path = Paths.get(file);
|
||||
ourLog.info("Exporting ConceptMap to CSV: {}", path);
|
||||
try (
|
||||
Writer writer = Files.newBufferedWriter(path);
|
||||
CSVPrinter csvPrinter = new CSVPrinter(
|
||||
writer,
|
||||
CSVFormat
|
||||
.DEFAULT
|
||||
.withRecordSeparator("\n")
|
||||
.withHeader(Header.class)
|
||||
.withQuoteMode(QuoteMode.ALL));
|
||||
) {
|
||||
for (ConceptMapGroupComponent group : theConceptMap.getGroup()) {
|
||||
for (SourceElementComponent element : group.getElement()) {
|
||||
for (ConceptMap.TargetElementComponent target : element.getTarget()) {
|
||||
try (Writer writer = Files.newBufferedWriter(path)) {
|
||||
|
||||
List<String> columns = new ArrayList<>();
|
||||
columns.add(defaultString(group.getSource()));
|
||||
columns.add(defaultString(group.getSourceVersion()));
|
||||
columns.add(defaultString(group.getTarget()));
|
||||
columns.add(defaultString(group.getTargetVersion()));
|
||||
columns.add(defaultString(element.getCode()));
|
||||
columns.add(defaultString(element.getDisplay()));
|
||||
columns.add(defaultString(target.getCode()));
|
||||
columns.add(defaultString(target.getDisplay()));
|
||||
columns.add(defaultString(target.getEquivalence().toCode()));
|
||||
columns.add(defaultString(target.getComment()));
|
||||
CSVFormat format = CSVFormat.DEFAULT
|
||||
.withRecordSeparator("\n")
|
||||
.withHeader(Header.class)
|
||||
.withQuoteMode(QuoteMode.ALL);
|
||||
try (CSVPrinter csvPrinter = new CSVPrinter(writer, format)) {
|
||||
for (ConceptMapGroupComponent group : theConceptMap.getGroup()) {
|
||||
for (SourceElementComponent element : group.getElement()) {
|
||||
for (ConceptMap.TargetElementComponent target : element.getTarget()) {
|
||||
|
||||
csvPrinter.printRecord(columns);
|
||||
List<String> columns = new ArrayList<>();
|
||||
columns.add(defaultString(group.getSource()));
|
||||
columns.add(defaultString(group.getSourceVersion()));
|
||||
columns.add(defaultString(group.getTarget()));
|
||||
columns.add(defaultString(group.getTargetVersion()));
|
||||
columns.add(defaultString(element.getCode()));
|
||||
columns.add(defaultString(element.getDisplay()));
|
||||
columns.add(defaultString(target.getCode()));
|
||||
columns.add(defaultString(target.getDisplay()));
|
||||
columns.add(defaultString(target.getEquivalence().toCode()));
|
||||
columns.add(defaultString(target.getComment()));
|
||||
|
||||
csvPrinter.printRecord(columns);
|
||||
}
|
||||
}
|
||||
}
|
||||
csvPrinter.flush();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new InternalErrorException(ioe);
|
||||
|
|
|
@ -91,7 +91,7 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ package ca.uhn.fhir.cli;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
|
@ -44,7 +44,7 @@ public class ToggleSearchParametersCommand extends BaseCommand {
|
|||
Options options = new Options();
|
||||
addFhirVersionOption(options);
|
||||
addBaseUrlOption(options);
|
||||
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.SCT_URI + ")");
|
||||
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + ITermLoaderSvc.SCT_URI + ")");
|
||||
addBasicAuthOption(options);
|
||||
return options;
|
||||
}
|
||||
|
|
|
@ -20,37 +20,39 @@ package ca.uhn.fhir.cli;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
|
||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl;
|
||||
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.util.AttachmentUtil;
|
||||
import ca.uhn.fhir.util.FileUtil;
|
||||
import ca.uhn.fhir.util.ParametersUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Charsets;
|
||||
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.input.CountingInputStream;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
import org.hl7.fhir.instance.model.api.ICompositeType;
|
||||
import org.hl7.fhir.r4.model.CodeSystem;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.io.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
public class UploadTerminologyCommand extends BaseCommand {
|
||||
public static final String UPLOAD_TERMINOLOGY = "upload-terminology";
|
||||
// TODO: Don't use qualified names for loggers in HAPI CLI.
|
||||
static final String UPLOAD_TERMINOLOGY = "upload-terminology";
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UploadTerminologyCommand.class);
|
||||
private static final long DEFAULT_TRANSFER_SIZE_LIMIT = 10 * FileUtils.ONE_MB;
|
||||
private static long ourTransferSizeLimit = DEFAULT_TRANSFER_SIZE_LIMIT;
|
||||
|
||||
@Override
|
||||
public String getCommandDescription() {
|
||||
|
@ -68,9 +70,8 @@ public class UploadTerminologyCommand extends BaseCommand {
|
|||
|
||||
addFhirVersionOption(options);
|
||||
addBaseUrlOption(options);
|
||||
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.SCT_URI + ")");
|
||||
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + ITermLoaderSvc.SCT_URI + ")");
|
||||
addOptionalOption(options, "d", "data", true, "Local file to use to upload (can be a raw file or a ZIP containing the raw file)");
|
||||
addOptionalOption(options, null, "custom", false, "Indicates that this upload uses the HAPI FHIR custom external terminology format");
|
||||
addOptionalOption(options, "m", "mode", true, "The upload mode: SNAPSHOT (default), ADD, REMOVE");
|
||||
addBasicAuthOption(options);
|
||||
addVerboseLoggingOption(options);
|
||||
|
@ -109,111 +110,159 @@ public class UploadTerminologyCommand extends BaseCommand {
|
|||
|
||||
switch (mode) {
|
||||
case SNAPSHOT:
|
||||
uploadSnapshot(inputParameters, termUrl, datafile, theCommandLine, client);
|
||||
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM);
|
||||
break;
|
||||
case ADD:
|
||||
uploadDelta(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD, false);
|
||||
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD);
|
||||
break;
|
||||
case REMOVE:
|
||||
uploadDelta(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE, true);
|
||||
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void uploadDelta(CommandLine theCommandLine, String theTermUrl, String[] theDatafile, IGenericClient theClient, IBaseParameters theInputParameters, String theOperationName, boolean theFlatten) {
|
||||
ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputParameters, "url", theTermUrl);
|
||||
private void invokeOperation(String theTermUrl, String[] theDatafile, IGenericClient theClient, IBaseParameters theInputParameters, String theOperationName) throws ParseException {
|
||||
ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_SYSTEM, theTermUrl);
|
||||
|
||||
List<IHapiTerminologyLoaderSvc.FileDescriptor> fileDescriptors = new ArrayList<>();
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8);
|
||||
int compressedSourceBytesCount = 0;
|
||||
int compressedFileCount = 0;
|
||||
boolean haveCompressedContents = false;
|
||||
try {
|
||||
for (String nextDataFile : theDatafile) {
|
||||
|
||||
try (FileInputStream fileInputStream = new FileInputStream(nextDataFile)) {
|
||||
if (nextDataFile.endsWith(".csv")) {
|
||||
|
||||
ourLog.info("Compressing and adding file: {}", nextDataFile);
|
||||
ZipEntry nextEntry = new ZipEntry(stripPath(nextDataFile));
|
||||
zipOutputStream.putNextEntry(nextEntry);
|
||||
|
||||
CountingInputStream countingInputStream = new CountingInputStream(fileInputStream);
|
||||
IOUtils.copy(countingInputStream, zipOutputStream);
|
||||
haveCompressedContents = true;
|
||||
compressedSourceBytesCount += countingInputStream.getCount();
|
||||
|
||||
zipOutputStream.flush();
|
||||
ourLog.info("Finished compressing {}", nextDataFile);
|
||||
|
||||
} else if (nextDataFile.endsWith(".zip")) {
|
||||
|
||||
ourLog.info("Adding ZIP file: {}", nextDataFile);
|
||||
String fileName = "file:" + nextDataFile;
|
||||
addFileToRequestBundle(theInputParameters, fileName, IOUtils.toByteArray(fileInputStream));
|
||||
|
||||
} else if (nextDataFile.endsWith(".json") || nextDataFile.endsWith(".xml")) {
|
||||
|
||||
ourLog.info("Adding CodeSystem resource file: {}", nextDataFile);
|
||||
|
||||
String contents = IOUtils.toString(fileInputStream, Charsets.UTF_8);
|
||||
EncodingEnum encoding = EncodingEnum.detectEncodingNoDefault(contents);
|
||||
if (encoding == null) {
|
||||
throw new ParseException("Could not detect FHIR encoding for file: " + nextDataFile);
|
||||
}
|
||||
|
||||
CodeSystem resource = encoding.newParser(myFhirCtx).parseResource(CodeSystem.class, contents);
|
||||
ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_CODESYSTEM, resource);
|
||||
|
||||
} else {
|
||||
|
||||
throw new ParseException("Don't know how to handle file: " + nextDataFile);
|
||||
|
||||
for (String next : theDatafile) {
|
||||
try (FileInputStream inputStream = new FileInputStream(next)) {
|
||||
byte[] bytes = IOUtils.toByteArray(inputStream);
|
||||
fileDescriptors.add(new IHapiTerminologyLoaderSvc.FileDescriptor() {
|
||||
@Override
|
||||
public String getFilename() {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
});
|
||||
}
|
||||
zipOutputStream.flush();
|
||||
zipOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new ParseException(e.toString());
|
||||
}
|
||||
|
||||
if (haveCompressedContents) {
|
||||
byte[] compressedBytes = byteArrayOutputStream.toByteArray();
|
||||
ourLog.info("Compressed {} bytes in {} file(s) into {} bytes", FileUtil.formatFileSize(compressedSourceBytesCount), compressedFileCount, FileUtil.formatFileSize(compressedBytes.length));
|
||||
|
||||
addFileToRequestBundle(theInputParameters, "file:/files.zip", compressedBytes);
|
||||
}
|
||||
|
||||
ourLog.info("Beginning upload - This may take a while...");
|
||||
|
||||
if (ourLog.isDebugEnabled() || "true".equals(System.getProperty("test"))) {
|
||||
ourLog.info("Submitting parameters: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theInputParameters));
|
||||
}
|
||||
|
||||
IBaseParameters response;
|
||||
try {
|
||||
response = theClient
|
||||
.operation()
|
||||
.onType(myFhirCtx.getResourceDefinition("CodeSystem").getImplementingClass())
|
||||
.named(theOperationName)
|
||||
.withParameters(theInputParameters)
|
||||
.execute();
|
||||
} catch (BaseServerResponseException e) {
|
||||
if (e.getOperationOutcome() != null) {
|
||||
ourLog.error("Received the following response:\n{}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
ourLog.info("Upload complete!");
|
||||
ourLog.info("Response:\n{}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response));
|
||||
}
|
||||
|
||||
private void addFileToRequestBundle(IBaseParameters theInputParameters, String theFileName, byte[] theBytes) {
|
||||
|
||||
byte[] bytes = theBytes;
|
||||
String fileName = theFileName;
|
||||
|
||||
if (bytes.length > ourTransferSizeLimit) {
|
||||
ourLog.info("File size is greater than {} - Going to use a local file reference instead of a direct HTTP transfer. Note that this will only work when executing this command on the same server as the FHIR server itself.", FileUtil.formatFileSize(ourTransferSizeLimit));
|
||||
|
||||
String suffix = fileName.substring(fileName.lastIndexOf("."));
|
||||
try {
|
||||
File tempFile = File.createTempFile("hapi-fhir-cli", suffix);
|
||||
tempFile.deleteOnExit();
|
||||
try (OutputStream fileOutputStream = new FileOutputStream(tempFile, false)) {
|
||||
fileOutputStream.write(bytes);
|
||||
bytes = null;
|
||||
fileName = "localfile:" + tempFile.getAbsolutePath();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new CommandFailureException("Failed to read from file \"" + next + "\": " + e.getMessage());
|
||||
throw new CommandFailureException(e);
|
||||
}
|
||||
}
|
||||
|
||||
TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion();
|
||||
TerminologyLoaderSvcImpl.LoadedFileDescriptors descriptors = new TerminologyLoaderSvcImpl.LoadedFileDescriptors(fileDescriptors);
|
||||
TerminologyLoaderSvcImpl.processCustomTerminologyFiles(descriptors, codeSystemVersion);
|
||||
|
||||
CodeSystem codeSystem = new CodeSystem();
|
||||
codeSystem.setUrl(theTermUrl);
|
||||
addCodesToCodeSystem(codeSystemVersion.getConcepts(), codeSystem.getConcept(), theFlatten);
|
||||
|
||||
ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, "value", codeSystem);
|
||||
|
||||
if (theCommandLine.hasOption("custom")) {
|
||||
ParametersUtil.addParameterToParametersCode(myFhirCtx, theInputParameters, "contentMode", "custom");
|
||||
ICompositeType attachment = AttachmentUtil.newInstance(myFhirCtx);
|
||||
AttachmentUtil.setUrl(myFhirCtx, attachment, fileName);
|
||||
if (bytes != null) {
|
||||
AttachmentUtil.setData(myFhirCtx, attachment, bytes);
|
||||
}
|
||||
|
||||
ourLog.info("Beginning upload - This may take a while...");
|
||||
|
||||
IBaseParameters response = theClient
|
||||
.operation()
|
||||
.onType(myFhirCtx.getResourceDefinition("CodeSystem").getImplementingClass())
|
||||
.named(theOperationName)
|
||||
.withParameters(theInputParameters)
|
||||
.execute();
|
||||
|
||||
ourLog.info("Upload complete!");
|
||||
ourLog.info("Response:\n{}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response));
|
||||
}
|
||||
|
||||
private void addCodesToCodeSystem(Collection<TermConcept> theSourceConcepts, List<CodeSystem.ConceptDefinitionComponent> theTargetConcept, boolean theFlatten) {
|
||||
for (TermConcept nextSourceConcept : theSourceConcepts) {
|
||||
|
||||
CodeSystem.ConceptDefinitionComponent nextTarget = new CodeSystem.ConceptDefinitionComponent();
|
||||
nextTarget.setCode(nextSourceConcept.getCode());
|
||||
nextTarget.setDisplay(nextSourceConcept.getDisplay());
|
||||
theTargetConcept.add(nextTarget);
|
||||
|
||||
List<TermConcept> children = nextSourceConcept.getChildren().stream().map(t -> t.getChild()).collect(Collectors.toList());
|
||||
if (theFlatten) {
|
||||
addCodesToCodeSystem(children, theTargetConcept, theFlatten);
|
||||
} else {
|
||||
addCodesToCodeSystem(children, nextTarget.getConcept(), theFlatten);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadSnapshot(IBaseParameters theInputparameters, String theTermUrl, String[] theDatafile, CommandLine theCommandLine, IGenericClient theClient) {
|
||||
ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputparameters, "url", theTermUrl);
|
||||
for (String next : theDatafile) {
|
||||
ParametersUtil.addParameterToParametersString(myFhirCtx, theInputparameters, "localfile", next);
|
||||
}
|
||||
if (theCommandLine.hasOption("custom")) {
|
||||
ParametersUtil.addParameterToParametersCode(myFhirCtx, theInputparameters, "contentMode", "custom");
|
||||
}
|
||||
|
||||
ourLog.info("Beginning upload - This may take a while...");
|
||||
|
||||
IBaseParameters response = theClient
|
||||
.operation()
|
||||
.onType(myFhirCtx.getResourceDefinition("CodeSystem").getImplementingClass())
|
||||
.named(JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM)
|
||||
.withParameters(theInputparameters)
|
||||
.execute();
|
||||
|
||||
ourLog.info("Upload complete!");
|
||||
ourLog.info("Response:\n{}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response));
|
||||
ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_FILE, attachment);
|
||||
}
|
||||
|
||||
private enum ModeEnum {
|
||||
SNAPSHOT, ADD, REMOVE
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void setTransferSizeLimitForUnitTest(long theTransferSizeLimit) {
|
||||
if (theTransferSizeLimit <= 0) {
|
||||
ourTransferSizeLimit = DEFAULT_TRANSFER_SIZE_LIMIT;
|
||||
}else {
|
||||
ourTransferSizeLimit = theTransferSizeLimit;
|
||||
}
|
||||
}
|
||||
|
||||
static String stripPath(String thePath) {
|
||||
String retVal = thePath;
|
||||
if (retVal.contains("/")) {
|
||||
retVal = retVal.substring(retVal.lastIndexOf("/"));
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,7 +34,13 @@
|
|||
<!--
|
||||
It's useful to have this log when uploading big terminologies
|
||||
-->
|
||||
<logger name="ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl" additivity="false" level="info">
|
||||
<logger name="ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl" additivity="false" level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
<logger name="ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl" additivity="false" level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
<logger name="ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl" additivity="false" level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.test.utilities.LoggingRule;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
|
@ -24,10 +25,9 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
|
||||
public class ExportConceptMapToCsvCommandDstu3Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandDstu3Test.class);
|
||||
private static final String CM_URL = "http://example.com/conceptmap";
|
||||
|
@ -36,7 +36,7 @@ public class ExportConceptMapToCsvCommandDstu3Test {
|
|||
private static final String CS_URL_1 = "http://example.com/codesystem/1";
|
||||
private static final String CS_URL_2 = "http://example.com/codesystem/2";
|
||||
private static final String CS_URL_3 = "http://example.com/codesystem/3";
|
||||
private static final String FILE = "./target/output.csv";
|
||||
private static final String FILE = "./target/output_dstu3.csv";
|
||||
|
||||
private static String ourBase;
|
||||
private static IGenericClient ourClient;
|
||||
|
@ -44,13 +44,47 @@ public class ExportConceptMapToCsvCommandDstu3Test {
|
|||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static String ourVersion = "dstu3";
|
||||
@Rule
|
||||
public LoggingRule myLoggingRule = new LoggingRule();
|
||||
|
||||
static {
|
||||
System.setProperty("test", "true");
|
||||
}
|
||||
|
||||
@Rule
|
||||
public LoggingRule myLoggingRule = new LoggingRule();
|
||||
|
||||
@Test
|
||||
public void testExportConceptMapToCsvCommand() throws IOException {
|
||||
ourLog.debug("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[]{"export-conceptmap-to-csv",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
||||
"-l"});
|
||||
await().until(() -> new File(FILE).exists());
|
||||
|
||||
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
|
||||
|
||||
ourLog.info("Going to read file: {}", FILE);
|
||||
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
|
||||
assertEquals(expected, result);
|
||||
|
||||
FileUtils.deleteQuietly(new File(FILE));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
JettyUtil.closeServer(ourServer);
|
||||
|
@ -72,7 +106,7 @@ public class ExportConceptMapToCsvCommandDstu3Test {
|
|||
ourServer.setHandler(servletHandler);
|
||||
|
||||
JettyUtil.startServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
|
||||
ourBase = "http://localhost:" + ourPort;
|
||||
|
||||
|
@ -81,38 +115,6 @@ public class ExportConceptMapToCsvCommandDstu3Test {
|
|||
ourClient.create().resource(createConceptMap()).execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportConceptMapToCsvCommand() throws IOException {
|
||||
ourLog.debug("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[] {"export-conceptmap-to-csv",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
||||
"-l"});
|
||||
|
||||
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
|
||||
|
||||
ourLog.info("Going to read file: {}", FILE);
|
||||
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
|
||||
assertEquals(expected, result);
|
||||
|
||||
FileUtils.deleteQuietly(new File(FILE));
|
||||
}
|
||||
|
||||
static ConceptMap createConceptMap() {
|
||||
ConceptMap conceptMap = new ConceptMap();
|
||||
conceptMap
|
||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
@ -22,10 +23,9 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
|
||||
public class ExportConceptMapToCsvCommandR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandR4Test.class);
|
||||
private static final String CM_URL = "http://example.com/conceptmap";
|
||||
|
@ -34,7 +34,7 @@ public class ExportConceptMapToCsvCommandR4Test {
|
|||
private static final String CS_URL_1 = "http://example.com/codesystem/1";
|
||||
private static final String CS_URL_2 = "http://example.com/codesystem/2";
|
||||
private static final String CS_URL_3 = "http://example.com/codesystem/3";
|
||||
private static final String FILE = new File("./target/output.csv").getAbsolutePath();
|
||||
private static final String FILE = new File("./target/output_r4.csv").getAbsolutePath();
|
||||
|
||||
private static String ourBase;
|
||||
private static IGenericClient ourClient;
|
||||
|
@ -47,6 +47,37 @@ public class ExportConceptMapToCsvCommandR4Test {
|
|||
System.setProperty("test", "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportConceptMapToCsvCommand() throws IOException {
|
||||
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[]{"export-conceptmap-to-csv",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
||||
"-l"});
|
||||
await().until(() -> new File(FILE).exists());
|
||||
|
||||
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
|
||||
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
|
||||
assertEquals(expected, result);
|
||||
|
||||
FileUtils.deleteQuietly(new File(FILE));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
JettyUtil.closeServer(ourServer);
|
||||
|
@ -68,7 +99,7 @@ public class ExportConceptMapToCsvCommandR4Test {
|
|||
ourServer.setHandler(servletHandler);
|
||||
|
||||
JettyUtil.startServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
|
||||
ourBase = "http://localhost:" + ourPort;
|
||||
|
||||
|
@ -77,36 +108,6 @@ public class ExportConceptMapToCsvCommandR4Test {
|
|||
ourClient.create().resource(createConceptMap()).execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportConceptMapToCsvCommand() throws IOException {
|
||||
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[] {"export-conceptmap-to-csv",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
||||
"-l"});
|
||||
|
||||
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
|
||||
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
|
||||
assertEquals(expected, result);
|
||||
|
||||
FileUtils.deleteQuietly(new File(FILE));
|
||||
}
|
||||
|
||||
static ConceptMap createConceptMap() {
|
||||
ConceptMap conceptMap = new ConceptMap();
|
||||
conceptMap
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package ca.uhn.fhir.cli;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.test.BaseTest;
|
||||
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.jpa.term.UploadStatistics;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
@ -14,6 +16,7 @@ import org.eclipse.jetty.servlet.ServletHolder;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.hl7.fhir.r4.model.CodeSystem;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -25,15 +28,19 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class UploadTerminologyCommandTest {
|
||||
public class UploadTerminologyCommandTest extends BaseTest {
|
||||
|
||||
static {
|
||||
System.setProperty("test", "true");
|
||||
|
@ -42,26 +49,28 @@ public class UploadTerminologyCommandTest {
|
|||
private Server myServer;
|
||||
private FhirContext myCtx = FhirContext.forR4();
|
||||
@Mock
|
||||
private IHapiTerminologyLoaderSvc myTerminologyLoaderSvc;
|
||||
@Mock
|
||||
private IHapiTerminologySvc myTerminologySvc;
|
||||
private ITermLoaderSvc myTermLoaderSvc;
|
||||
@Captor
|
||||
private ArgumentCaptor<List<IHapiTerminologyLoaderSvc.FileDescriptor>> myDescriptorList;
|
||||
@Captor
|
||||
private ArgumentCaptor<CodeSystem> myCodeSystemCaptor;
|
||||
private ArgumentCaptor<List<ITermLoaderSvc.FileDescriptor>> myDescriptorListCaptor;
|
||||
|
||||
private int myPort;
|
||||
private String myConceptsFileName = "target/concepts.csv";
|
||||
private String myHierarchyFileName = "target/hierarchy.csv";
|
||||
private File myConceptsFile = new File(myConceptsFileName);
|
||||
private String myHierarchyFileName = "target/hierarchy.csv";
|
||||
private File myHierarchyFile = new File(myHierarchyFileName);
|
||||
private String myCodeSystemFileName = "target/codesystem.json";
|
||||
private File myCodeSystemFile = new File(myCodeSystemFileName);
|
||||
private String myTextFileName = "target/hello.txt";
|
||||
private File myTextFile = new File(myTextFileName);
|
||||
private File myArchiveFile;
|
||||
private String myArchiveFileName;
|
||||
|
||||
@Test
|
||||
public void testTerminologyUpload_AddDelta() throws IOException {
|
||||
public void testDeltaAdd() throws IOException {
|
||||
|
||||
writeConceptAndHierarchyFiles();
|
||||
|
||||
when(myTerminologySvc.applyDeltaCodesystemsAdd(eq("http://foo"), any(), any())).thenReturn(new AtomicInteger(100));
|
||||
when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
|
||||
|
||||
App.main(new String[]{
|
||||
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
|
||||
|
@ -73,25 +82,139 @@ public class UploadTerminologyCommandTest {
|
|||
"-d", myHierarchyFileName
|
||||
});
|
||||
|
||||
verify(myTerminologySvc, times(1)).applyDeltaCodesystemsAdd(any(), isNull(), myCodeSystemCaptor.capture());
|
||||
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any());
|
||||
|
||||
CodeSystem codeSystem = myCodeSystemCaptor.getValue();
|
||||
assertEquals(1, codeSystem.getConcept().size());
|
||||
assertEquals("http://foo", codeSystem.getUrl());
|
||||
assertEquals("ANIMALS", codeSystem.getConcept().get(0).getCode());
|
||||
assertEquals("Animals", codeSystem.getConcept().get(0).getDisplay());
|
||||
assertEquals(2, codeSystem.getConcept().get(0).getConcept().size());
|
||||
assertEquals("CATS", codeSystem.getConcept().get(0).getConcept().get(0).getCode());
|
||||
assertEquals("Cats", codeSystem.getConcept().get(0).getConcept().get(0).getDisplay());
|
||||
assertEquals("DOGS", codeSystem.getConcept().get(0).getConcept().get(1).getCode());
|
||||
assertEquals("Dogs", codeSystem.getConcept().get(0).getConcept().get(1).getDisplay());
|
||||
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
|
||||
assertEquals(1, listOfDescriptors.size());
|
||||
assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename());
|
||||
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTerminologyUpload_RemoveDelta() throws IOException {
|
||||
public void testDeltaAddUsingCodeSystemResource() throws IOException {
|
||||
|
||||
try (FileWriter w = new FileWriter(myCodeSystemFile, false)) {
|
||||
CodeSystem cs = new CodeSystem();
|
||||
cs.addConcept().setCode("CODE").setDisplay("Display");
|
||||
myCtx.newJsonParser().encodeResourceToWriter(cs, w);
|
||||
}
|
||||
|
||||
when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
|
||||
|
||||
App.main(new String[]{
|
||||
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
|
||||
"-v", "r4",
|
||||
"-m", "ADD",
|
||||
"-t", "http://localhost:" + myPort,
|
||||
"-u", "http://foo",
|
||||
"-d", myCodeSystemFileName
|
||||
});
|
||||
|
||||
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any());
|
||||
|
||||
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
|
||||
assertEquals(1, listOfDescriptors.size());
|
||||
assertEquals("concepts.csv", listOfDescriptors.get(0).getFilename());
|
||||
String uploadFile = IOUtils.toString(listOfDescriptors.get(0).getInputStream(), Charsets.UTF_8);
|
||||
assertThat(uploadFile, containsString("CODE,Display"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeltaAddInvalidResource() throws IOException {
|
||||
|
||||
try (FileWriter w = new FileWriter(myCodeSystemFile, false)) {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
myCtx.newJsonParser().encodeResourceToWriter(patient, w);
|
||||
}
|
||||
|
||||
try {
|
||||
App.main(new String[]{
|
||||
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
|
||||
"-v", "r4",
|
||||
"-m", "ADD",
|
||||
"-t", "http://localhost:" + myPort,
|
||||
"-u", "http://foo",
|
||||
"-d", myCodeSystemFileName
|
||||
});
|
||||
fail();
|
||||
} catch (Error e) {
|
||||
assertThat(e.toString(), containsString("Incorrect resource type found, expected \"CodeSystem\" but found \"Patient\""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeltaAddInvalidFileType() throws IOException {
|
||||
|
||||
try (FileWriter w = new FileWriter(myTextFileName, false)) {
|
||||
w.append("Help I'm a Bug");
|
||||
}
|
||||
|
||||
try {
|
||||
App.main(new String[]{
|
||||
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
|
||||
"-v", "r4",
|
||||
"-m", "ADD",
|
||||
"-t", "http://localhost:" + myPort,
|
||||
"-u", "http://foo",
|
||||
"-d", myTextFileName
|
||||
});
|
||||
fail();
|
||||
} catch (Error e) {
|
||||
assertThat(e.toString(), containsString("Don't know how to handle file:"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeltaAddUsingCompressedFile() throws IOException {
|
||||
|
||||
writeConceptAndHierarchyFiles();
|
||||
writeArchiveFile(myConceptsFile, myHierarchyFile);
|
||||
|
||||
when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
|
||||
|
||||
App.main(new String[]{
|
||||
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
|
||||
"-v", "r4",
|
||||
"-m", "ADD",
|
||||
"-t", "http://localhost:" + myPort,
|
||||
"-u", "http://foo",
|
||||
"-d", myArchiveFileName
|
||||
});
|
||||
|
||||
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any());
|
||||
|
||||
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
|
||||
assertEquals(1, listOfDescriptors.size());
|
||||
assertThat(listOfDescriptors.get(0).getFilename(), matchesPattern("^file:.*temp.*\\.zip$"));
|
||||
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeltaAddInvalidFileName() throws IOException {
|
||||
|
||||
writeConceptAndHierarchyFiles();
|
||||
|
||||
when(myTerminologySvc.applyDeltaCodesystemsRemove(eq("http://foo"), any())).thenReturn(new AtomicInteger(100));
|
||||
try {
|
||||
App.main(new String[]{
|
||||
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
|
||||
"-v", "r4",
|
||||
"-m", "ADD",
|
||||
"-t", "http://localhost:" + myPort,
|
||||
"-u", "http://foo",
|
||||
"-d", myConceptsFileName + "/foo.csv",
|
||||
"-d", myHierarchyFileName
|
||||
});
|
||||
} catch (Error e) {
|
||||
assertThat(e.toString(), Matchers.containsString("FileNotFoundException: target/concepts.csv/foo.csv"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeltaRemove() throws IOException {
|
||||
writeConceptAndHierarchyFiles();
|
||||
|
||||
when(myTermLoaderSvc.loadDeltaRemove(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
|
||||
|
||||
App.main(new String[]{
|
||||
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
|
||||
|
@ -103,46 +226,94 @@ public class UploadTerminologyCommandTest {
|
|||
"-d", myHierarchyFileName
|
||||
});
|
||||
|
||||
verify(myTerminologySvc, times(1)).applyDeltaCodesystemsRemove(any(), myCodeSystemCaptor.capture());
|
||||
verify(myTermLoaderSvc, times(1)).loadDeltaRemove(eq("http://foo"), myDescriptorListCaptor.capture(), any());
|
||||
|
||||
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
|
||||
assertEquals(1, listOfDescriptors.size());
|
||||
assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename());
|
||||
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
|
||||
|
||||
CodeSystem codeSystem = myCodeSystemCaptor.getValue();
|
||||
assertEquals(3, codeSystem.getConcept().size());
|
||||
assertEquals("http://foo", codeSystem.getUrl());
|
||||
assertEquals("ANIMALS", codeSystem.getConcept().get(0).getCode());
|
||||
assertEquals("Animals", codeSystem.getConcept().get(0).getDisplay());
|
||||
assertEquals("CATS", codeSystem.getConcept().get(1).getCode());
|
||||
assertEquals("Cats", codeSystem.getConcept().get(1).getDisplay());
|
||||
assertEquals("DOGS", codeSystem.getConcept().get(2).getCode());
|
||||
assertEquals("Dogs", codeSystem.getConcept().get(2).getDisplay());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTerminologyUpload_Snapshot() throws IOException {
|
||||
public void testSnapshot() throws IOException {
|
||||
|
||||
writeConceptAndHierarchyFiles();
|
||||
|
||||
when(myTerminologyLoaderSvc.loadCustom(eq("http://foo"), any(), any())).thenReturn(new IHapiTerminologyLoaderSvc.UploadStatistics(100, new IdType("CodeSystem/123")));
|
||||
when(myTermLoaderSvc.loadCustom(any(), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
|
||||
|
||||
App.main(new String[]{
|
||||
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
|
||||
"-v", "r4",
|
||||
"-m", "SNAPSHOT",
|
||||
"--custom",
|
||||
"-t", "http://localhost:" + myPort,
|
||||
"-u", "http://foo",
|
||||
"-d", myConceptsFileName,
|
||||
"-d", myHierarchyFileName
|
||||
});
|
||||
|
||||
verify(myTerminologyLoaderSvc, times(1)).loadCustom(any(), myDescriptorList.capture(), any());
|
||||
verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any());
|
||||
|
||||
List<IHapiTerminologyLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorList.getValue();
|
||||
assertEquals(2, listOfDescriptors.size());
|
||||
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
|
||||
assertEquals(1, listOfDescriptors.size());
|
||||
assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename());
|
||||
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
|
||||
}
|
||||
|
||||
assertThat(listOfDescriptors.get(0).getFilename(), Matchers.endsWith("concepts.csv"));
|
||||
assertInputStreamEqualsFile(myConceptsFile, listOfDescriptors.get(0).getInputStream());
|
||||
assertThat(listOfDescriptors.get(1).getFilename(), Matchers.endsWith("hierarchy.csv"));
|
||||
assertInputStreamEqualsFile(myHierarchyFile, listOfDescriptors.get(1).getInputStream());
|
||||
/**
|
||||
* When transferring large files, we use a local file to store the binary instead of
|
||||
* using HTTP to transfer a giant base 64 encoded attachment. Hopefully we can
|
||||
* replace this with a bulk data import at some point when that gets implemented.
|
||||
*/
|
||||
@Test
|
||||
public void testSnapshotLargeFile() throws IOException {
|
||||
UploadTerminologyCommand.setTransferSizeLimitForUnitTest(10);
|
||||
|
||||
writeConceptAndHierarchyFiles();
|
||||
|
||||
when(myTermLoaderSvc.loadCustom(any(), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
|
||||
|
||||
App.main(new String[]{
|
||||
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
|
||||
"-v", "r4",
|
||||
"-m", "SNAPSHOT",
|
||||
"-t", "http://localhost:" + myPort,
|
||||
"-u", "http://foo",
|
||||
"-d", myConceptsFileName,
|
||||
"-d", myHierarchyFileName
|
||||
});
|
||||
|
||||
verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any());
|
||||
|
||||
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
|
||||
assertEquals(1, listOfDescriptors.size());
|
||||
assertThat(listOfDescriptors.get(0).getFilename(), matchesPattern(".*\\.zip$"));
|
||||
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
|
||||
}
|
||||
|
||||
private void writeArchiveFile(File... theFiles) throws IOException {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8);
|
||||
|
||||
for (File next : theFiles) {
|
||||
ZipEntry nextEntry = new ZipEntry(UploadTerminologyCommand.stripPath(next.getAbsolutePath()));
|
||||
zipOutputStream.putNextEntry(nextEntry);
|
||||
|
||||
try (FileInputStream fileInputStream = new FileInputStream(next)) {
|
||||
IOUtils.copy(fileInputStream, zipOutputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
zipOutputStream.flush();
|
||||
zipOutputStream.close();
|
||||
|
||||
myArchiveFile = File.createTempFile("temp", ".zip");
|
||||
myArchiveFile.deleteOnExit();
|
||||
myArchiveFileName = myArchiveFile.getAbsolutePath();
|
||||
try (FileOutputStream fos = new FileOutputStream(myArchiveFile, false)) {
|
||||
fos.write(byteArrayOutputStream.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -161,13 +332,6 @@ public class UploadTerminologyCommandTest {
|
|||
}
|
||||
}
|
||||
|
||||
private void assertInputStreamEqualsFile(File theExpectedFile, InputStream theActualInputStream) throws IOException {
|
||||
try (FileInputStream fis = new FileInputStream(theExpectedFile)) {
|
||||
byte[] expectedBytes = IOUtils.toByteArray(fis);
|
||||
byte[] actualBytes = IOUtils.toByteArray(theActualInputStream);
|
||||
assertArrayEquals(expectedBytes, actualBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
|
@ -175,13 +339,18 @@ public class UploadTerminologyCommandTest {
|
|||
|
||||
FileUtils.deleteQuietly(myConceptsFile);
|
||||
FileUtils.deleteQuietly(myHierarchyFile);
|
||||
FileUtils.deleteQuietly(myArchiveFile);
|
||||
FileUtils.deleteQuietly(myCodeSystemFile);
|
||||
FileUtils.deleteQuietly(myTextFile);
|
||||
|
||||
UploadTerminologyCommand.setTransferSizeLimitForUnitTest(-1);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void start() throws Exception {
|
||||
public void before() throws Exception {
|
||||
myServer = new Server(0);
|
||||
|
||||
TerminologyUploaderProvider provider = new TerminologyUploaderProvider(myCtx, myTerminologyLoaderSvc, myTerminologySvc);
|
||||
TerminologyUploaderProvider provider = new TerminologyUploaderProvider(myCtx, myTermLoaderSvc);
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(myCtx);
|
||||
|
|
|
@ -51,23 +51,19 @@ public class OkHttpRestfulResponse extends BaseHttpResponse implements IHttpResp
|
|||
this.myResponse = theResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntitity() throws IOException {
|
||||
bufferEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntity() throws IOException {
|
||||
if (myEntityBuffered) {
|
||||
return;
|
||||
}
|
||||
InputStream responseEntity = readEntity();
|
||||
if (responseEntity != null) {
|
||||
myEntityBuffered = true;
|
||||
try {
|
||||
myEntityBytes = IOUtils.toByteArray(responseEntity);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new InternalErrorException(e);
|
||||
try (InputStream responseEntity = readEntity()) {
|
||||
if (responseEntity != null) {
|
||||
myEntityBuffered = true;
|
||||
try {
|
||||
myEntityBytes = IOUtils.toByteArray(responseEntity);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.client.apache;
|
|||
*/
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.rest.client.impl.BaseHttpResponse;
|
||||
|
@ -53,25 +54,19 @@ public class ApacheHttpResponse extends BaseHttpResponse implements IHttpRespons
|
|||
this.myResponse = theResponse;
|
||||
}
|
||||
|
||||
@Deprecated // override deprecated method
|
||||
@Override
|
||||
public void bufferEntitity() throws IOException {
|
||||
bufferEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntity() throws IOException {
|
||||
if (myEntityBuffered) {
|
||||
return;
|
||||
}
|
||||
InputStream respEntity = readEntity();
|
||||
if (respEntity != null) {
|
||||
this.myEntityBuffered = true;
|
||||
try {
|
||||
this.myEntityBytes = IOUtils.toByteArray(respEntity);
|
||||
} catch (IllegalStateException e) {
|
||||
// FIXME resouce leak
|
||||
throw new InternalErrorException(e);
|
||||
try (InputStream respEntity = readEntity()) {
|
||||
if (respEntity != null) {
|
||||
this.myEntityBuffered = true;
|
||||
try {
|
||||
this.myEntityBytes = IOUtils.toByteArray(respEntity);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +98,7 @@ public class ApacheHttpResponse extends BaseHttpResponse implements IHttpRespons
|
|||
if (Constants.STATUS_HTTP_204_NO_CONTENT != myResponse.getStatusLine().getStatusCode()) {
|
||||
ourLog.debug("Response did not specify a charset, defaulting to utf-8");
|
||||
}
|
||||
charset = Charset.forName("UTF-8");
|
||||
charset = StandardCharsets.UTF_8;
|
||||
}
|
||||
|
||||
return new InputStreamReader(readEntity(), charset);
|
||||
|
@ -115,11 +110,7 @@ public class ApacheHttpResponse extends BaseHttpResponse implements IHttpRespons
|
|||
if (myResponse.getAllHeaders() != null) {
|
||||
for (Header next : myResponse.getAllHeaders()) {
|
||||
String name = next.getName().toLowerCase();
|
||||
List<String> list = headers.get(name);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
headers.put(name, list);
|
||||
}
|
||||
List<String> list = headers.computeIfAbsent(name, k -> new ArrayList<>());
|
||||
list.add(next.getValue());
|
||||
}
|
||||
|
||||
|
|
|
@ -71,18 +71,18 @@ public class ApacheRestfulClientFactory extends RestfulClientFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ApacheHttpClient getHttpClient(String theServerBase) {
|
||||
protected synchronized ApacheHttpClient getHttpClient(String theServerBase) {
|
||||
return new ApacheHttpClient(getNativeHttpClient(), new StringBuilder(theServerBase), null, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams,
|
||||
public synchronized IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams,
|
||||
String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) {
|
||||
return new ApacheHttpClient(getNativeHttpClient(), theUrl, theIfNoneExistParams, theIfNoneExistString, theRequestType,
|
||||
theHeaders);
|
||||
}
|
||||
|
||||
public synchronized HttpClient getNativeHttpClient() {
|
||||
public HttpClient getNativeHttpClient() {
|
||||
if (myHttpClient == null) {
|
||||
|
||||
//FIXME potential resoource leak
|
||||
|
|
|
@ -71,52 +71,52 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getConnectionRequestTimeout() {
|
||||
public synchronized int getConnectionRequestTimeout() {
|
||||
return myConnectionRequestTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConnectTimeout() {
|
||||
public synchronized int getConnectTimeout() {
|
||||
return myConnectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the proxy username to authenticate with the HTTP proxy
|
||||
*/
|
||||
protected String getProxyUsername() {
|
||||
protected synchronized String getProxyUsername() {
|
||||
return myProxyUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the proxy password to authenticate with the HTTP proxy
|
||||
*/
|
||||
protected String getProxyPassword() {
|
||||
protected synchronized String getProxyPassword() {
|
||||
return myProxyPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProxyCredentials(String theUsername, String thePassword) {
|
||||
public synchronized void setProxyCredentials(String theUsername, String thePassword) {
|
||||
myProxyUsername = theUsername;
|
||||
myProxyPassword = thePassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerValidationModeEnum getServerValidationMode() {
|
||||
public synchronized ServerValidationModeEnum getServerValidationMode() {
|
||||
return myServerValidationMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSocketTimeout() {
|
||||
public synchronized int getSocketTimeout() {
|
||||
return mySocketTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPoolMaxTotal() {
|
||||
public synchronized int getPoolMaxTotal() {
|
||||
return myPoolMaxTotal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPoolMaxPerRoute() {
|
||||
public synchronized int getPoolMaxPerRoute() {
|
||||
return myPoolMaxPerRoute;
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setServerValidationMode(ServerValidationModeEnum theServerValidationMode) {
|
||||
public synchronized void setServerValidationMode(ServerValidationModeEnum theServerValidationMode) {
|
||||
Validate.notNull(theServerValidationMode, "theServerValidationMode may not be null");
|
||||
myServerValidationMode = theServerValidationMode;
|
||||
}
|
||||
|
@ -242,13 +242,13 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
|||
|
||||
@Deprecated // override deprecated method
|
||||
@Override
|
||||
public ServerValidationModeEnum getServerValidationModeEnum() {
|
||||
public synchronized ServerValidationModeEnum getServerValidationModeEnum() {
|
||||
return getServerValidationMode();
|
||||
}
|
||||
|
||||
@Deprecated // override deprecated method
|
||||
@Override
|
||||
public void setServerValidationModeEnum(ServerValidationModeEnum theServerValidationMode) {
|
||||
public synchronized void setServerValidationModeEnum(ServerValidationModeEnum theServerValidationMode) {
|
||||
setServerValidationMode(theServerValidationMode);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.client.interceptor;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -94,9 +95,7 @@ public class LoggingInterceptor implements IClientInterceptor {
|
|||
if (content != null) {
|
||||
myLog.info("Client request body:\n{}", content);
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
|
||||
} catch (IOException e) {
|
||||
} catch (IllegalStateException | IOException e) {
|
||||
myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
|
||||
}
|
||||
}
|
||||
|
@ -147,11 +146,8 @@ public class LoggingInterceptor implements IClientInterceptor {
|
|||
}
|
||||
|
||||
if (myLogResponseBody) {
|
||||
//TODO: Use of a deprecated method should be resolved.
|
||||
theResponse.bufferEntitity();
|
||||
InputStream respEntity = null;
|
||||
try {
|
||||
respEntity = theResponse.readEntity();
|
||||
theResponse.bufferEntity();
|
||||
try (InputStream respEntity = theResponse.readEntity()) {
|
||||
if (respEntity != null) {
|
||||
final byte[] bytes;
|
||||
try {
|
||||
|
@ -159,12 +155,10 @@ public class LoggingInterceptor implements IClientInterceptor {
|
|||
} catch (IllegalStateException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
myLog.info("Client response body:\n{}", new String(bytes, "UTF-8"));
|
||||
myLog.info("Client response body:\n{}", new String(bytes, StandardCharsets.UTF_8));
|
||||
} else {
|
||||
myLog.info("Client response body: (none)");
|
||||
}
|
||||
} finally {
|
||||
IOUtils.closeQuietly(respEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +172,9 @@ public class LoggingInterceptor implements IClientInterceptor {
|
|||
Iterator<String> values = theHeaders.get(key).iterator();
|
||||
while(values.hasNext()) {
|
||||
String value = values.next();
|
||||
b.append(key + ": " + value);
|
||||
b.append(key);
|
||||
b.append(": ");
|
||||
b.append(value);
|
||||
if (nameEntries.hasNext() || values.hasNext()) {
|
||||
b.append('\n');
|
||||
}
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-converter</artifactId>
|
||||
<!-- TODO: BND changed to jar temporarily -->
|
||||
<packaging>jar</packaging>
|
||||
<packaging>bundle</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -152,10 +151,6 @@
|
|||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<!--
|
||||
TODO: BND
|
||||
With this enabled, JAR file contains a bunch of .class files that
|
||||
seem to get copied in from org.hl7.fhir.convertors
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
|
@ -170,7 +165,6 @@
|
|||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
-->
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
@ -29,6 +29,8 @@ import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
|||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||
import org.hl7.fhir.converter.NullVersionConverterAdvisor30;
|
||||
import org.hl7.fhir.converter.NullVersionConverterAdvisor40;
|
||||
import org.hl7.fhir.convertors.*;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -20,6 +20,7 @@ package org.hl7.fhir.convertors;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertorAdvisor30;
|
||||
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
import org.hl7.fhir.dstu3.model.ValueSet;
|
|
@ -1,4 +1,4 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -20,6 +20,7 @@ package org.hl7.fhir.convertors;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertorAdvisor40;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.dstu2.model.Resource;
|
||||
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
|
|
@ -1,4 +1,4 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -20,6 +20,7 @@ package org.hl7.fhir.convertors;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertorAdvisor50;
|
||||
import org.hl7.fhir.dstu2.model.Resource;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
|
@ -1,7 +1,8 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertor_10_30;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.dstu2.model.Resource;
|
|
@ -1,7 +1,8 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertor_14_30;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.junit.Test;
|
||||
|
|
@ -87,6 +87,12 @@
|
|||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.28</version>
|
||||
</dependency>
|
||||
<!-- Needed for JEE/Servlet support -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -20,7 +20,7 @@ package ca.uhn.hapi.fhir.docs;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.convertors.NullVersionConverterAdvisor30;
|
||||
import org.hl7.fhir.converter.NullVersionConverterAdvisor30;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_10_30;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_14_30;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
|
|
@ -290,7 +290,7 @@ public class ValidatorExamples {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
|
||||
// TODO: implement (or return null if your implementation does not support this function)
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ public class IgPackValidationSupportDstu3 implements IValidationSupport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,11 +51,6 @@ public class JaxRsHttpResponse extends BaseHttpResponse implements IHttpResponse
|
|||
this.myResponse = theResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntitity() throws IOException {
|
||||
bufferEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntity() throws IOException {
|
||||
if(!myBufferedEntity && myResponse.hasEntity()) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- Note: HAPI projects use the "hapi-fhir" POM as their base to provide easy management. You do not need to use this in your own projects, so the "parent" tag and it's contents below may be removed
|
||||
<!-- Note: HAPI projects use the "hapi-fhir" POM as their base to provide easy management. You do not need to use this in your own projects, so the "parent" tag and it's contents below may be removed
|
||||
if you are using this file as a basis for your own project. -->
|
||||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
|
@ -83,12 +83,12 @@
|
|||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-test-utilities</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-test-utilities</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
@ -120,5 +120,4 @@
|
|||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
|
@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PayloadTooLargeException;
|
||||
import com.google.common.hash.HashFunction;
|
||||
import com.google.common.hash.Hashing;
|
||||
|
@ -34,6 +33,8 @@ import javax.annotation.Nonnull;
|
|||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
||||
private final SecureRandom myRandom;
|
||||
private final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
@ -66,7 +67,8 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
myMinimumBinarySize = theMinimumBinarySize;
|
||||
}
|
||||
|
||||
String newRandomId() {
|
||||
@Override
|
||||
public String newBlobId() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (int i = 0; i < ID_LENGTH; i++) {
|
||||
int nextInt = Math.abs(myRandom.nextInt());
|
||||
|
@ -89,13 +91,13 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
|
||||
@Nonnull
|
||||
CountingInputStream createCountingInputStream(InputStream theInputStream) {
|
||||
InputStream is = ByteStreams.limit(theInputStream, myMaximumBinarySize + 1L);
|
||||
InputStream is = ByteStreams.limit(theInputStream, getMaximumBinarySize() + 1L);
|
||||
return new CountingInputStream(is) {
|
||||
@Override
|
||||
public int getCount() {
|
||||
int retVal = super.getCount();
|
||||
if (retVal > myMaximumBinarySize) {
|
||||
throw new PayloadTooLargeException("Binary size exceeds maximum: " + myMaximumBinarySize);
|
||||
if (retVal > getMaximumBinarySize()) {
|
||||
throw new PayloadTooLargeException("Binary size exceeds maximum: " + getMaximumBinarySize());
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
@ -103,4 +105,11 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
}
|
||||
|
||||
|
||||
String provideIdForNewBlob(String theBlobIdOrNull) {
|
||||
String id = theBlobIdOrNull;
|
||||
if (isBlank(theBlobIdOrNull)) {
|
||||
id = newBlobId();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,20 +86,13 @@ public class BinaryAccessProvider {
|
|||
|
||||
IBinaryTarget target = findAttachmentForRequest(resource, path, theRequestDetails);
|
||||
|
||||
Optional<? extends IBaseExtension<?, ?>> attachmentId = target
|
||||
.getTarget()
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
|
||||
.findFirst();
|
||||
|
||||
Optional<String> attachmentId = target.getAttachmentId();
|
||||
if (attachmentId.isPresent()) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
IPrimitiveType<String> value = (IPrimitiveType<String>) attachmentId.get().getValue();
|
||||
String blobId = value.getValueAsString();
|
||||
String blobId = attachmentId.get();
|
||||
|
||||
IBinaryStorageSvc.StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(theResourceId, blobId);
|
||||
StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(theResourceId, blobId);
|
||||
if (blobDetails == null) {
|
||||
String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "unknownBlobId");
|
||||
throw new InvalidRequestException(msg);
|
||||
|
@ -179,7 +172,7 @@ public class BinaryAccessProvider {
|
|||
if (size > 0) {
|
||||
if (myBinaryStorageSvc != null) {
|
||||
if (myBinaryStorageSvc.shouldStoreBlob(size, theResourceId, requestContentType)) {
|
||||
IBinaryStorageSvc.StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(theResourceId, requestContentType, theRequestDetails.getInputStream());
|
||||
StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(theResourceId, null, requestContentType, theRequestDetails.getInputStream());
|
||||
size = storedDetails.getBytes();
|
||||
blobId = storedDetails.getBlobId();
|
||||
Validate.notBlank(blobId, "BinaryStorageSvc returned a null blob ID"); // should not happen
|
||||
|
@ -192,19 +185,7 @@ public class BinaryAccessProvider {
|
|||
size = bytes.length;
|
||||
target.setData(bytes);
|
||||
} else {
|
||||
|
||||
target
|
||||
.getTarget()
|
||||
.getExtension()
|
||||
.removeIf(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()));
|
||||
target.setData(null);
|
||||
|
||||
IBaseExtension<?, ?> ext = target.getTarget().addExtension();
|
||||
ext.setUrl(JpaConstants.EXT_EXTERNALIZED_BINARY_ID);
|
||||
ext.setUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED, Boolean.TRUE);
|
||||
IPrimitiveType<String> blobIdString = (IPrimitiveType<String>) myCtx.getElementDefinition("string").newInstance();
|
||||
blobIdString.setValueAsString(blobId);
|
||||
ext.setValue(blobIdString);
|
||||
replaceDataWithExtension(target, blobId);
|
||||
}
|
||||
|
||||
target.setContentType(requestContentType);
|
||||
|
@ -217,52 +198,81 @@ public class BinaryAccessProvider {
|
|||
return outcome.getResource();
|
||||
}
|
||||
|
||||
public void replaceDataWithExtension(IBinaryTarget theTarget, String theBlobId) {
|
||||
theTarget
|
||||
.getTarget()
|
||||
.getExtension()
|
||||
.removeIf(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()));
|
||||
theTarget.setData(null);
|
||||
|
||||
IBaseExtension<?, ?> ext = theTarget.getTarget().addExtension();
|
||||
ext.setUrl(JpaConstants.EXT_EXTERNALIZED_BINARY_ID);
|
||||
ext.setUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED, Boolean.TRUE);
|
||||
IPrimitiveType<String> blobIdString = (IPrimitiveType<String>) myCtx.getElementDefinition("string").newInstance();
|
||||
blobIdString.setValueAsString(theBlobId);
|
||||
ext.setValue(blobIdString);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private IBinaryTarget findAttachmentForRequest(IBaseResource theResource, String thePath, ServletRequestDetails theRequestDetails) {
|
||||
FhirContext ctx = theRequestDetails.getFhirContext();
|
||||
|
||||
Optional<IBase> type = ctx.newFluentPath().evaluateFirst(theResource, thePath, IBase.class);
|
||||
String resType = myCtx.getResourceDefinition(theResource).getName();
|
||||
Optional<IBase> type = myCtx.newFluentPath().evaluateFirst(theResource, thePath, IBase.class);
|
||||
String resType = this.myCtx.getResourceDefinition(theResource).getName();
|
||||
if (!type.isPresent()) {
|
||||
String msg = myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownPath", resType, thePath);
|
||||
String msg = this.myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownPath", resType, thePath);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
IBase element = type.get();
|
||||
|
||||
Optional<IBinaryTarget> binaryTarget = toBinaryTarget(element);
|
||||
|
||||
if (binaryTarget.isPresent() == false) {
|
||||
BaseRuntimeElementDefinition<?> def2 = myCtx.getElementDefinition(element.getClass());
|
||||
String msg = this.myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownType", resType, thePath, def2.getName());
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
return binaryTarget.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Optional<IBinaryTarget> toBinaryTarget(IBase theElement) {
|
||||
IBinaryTarget binaryTarget = null;
|
||||
|
||||
// Path is attachment
|
||||
BaseRuntimeElementDefinition<?> def = ctx.getElementDefinition(type.get().getClass());
|
||||
BaseRuntimeElementDefinition<?> def = myCtx.getElementDefinition(theElement.getClass());
|
||||
if (def.getName().equals("Attachment")) {
|
||||
ICompositeType attachment = (ICompositeType) type.get();
|
||||
return new IBinaryTarget() {
|
||||
ICompositeType attachment = (ICompositeType) theElement;
|
||||
binaryTarget = new IBinaryTarget() {
|
||||
@Override
|
||||
public void setSize(Integer theSize) {
|
||||
AttachmentUtil.setSize(myCtx, attachment, theSize);
|
||||
AttachmentUtil.setSize(BinaryAccessProvider.this.myCtx, attachment, theSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return AttachmentUtil.getOrCreateContentType(myCtx, attachment).getValueAsString();
|
||||
return AttachmentUtil.getOrCreateContentType(BinaryAccessProvider.this.myCtx, attachment).getValueAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData() {
|
||||
IPrimitiveType<byte[]> dataDt = AttachmentUtil.getOrCreateData(theRequestDetails.getFhirContext(), attachment);
|
||||
IPrimitiveType<byte[]> dataDt = AttachmentUtil.getOrCreateData(myCtx, attachment);
|
||||
return dataDt.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBaseHasExtensions getTarget() {
|
||||
return (IBaseHasExtensions) AttachmentUtil.getOrCreateData(theRequestDetails.getFhirContext(), attachment);
|
||||
return (IBaseHasExtensions) AttachmentUtil.getOrCreateData(myCtx, attachment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentType(String theContentType) {
|
||||
AttachmentUtil.setContentType(myCtx, attachment, theContentType);
|
||||
AttachmentUtil.setContentType(BinaryAccessProvider.this.myCtx, attachment, theContentType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setData(byte[] theBytes) {
|
||||
AttachmentUtil.setData(theRequestDetails.getFhirContext(), attachment, theBytes);
|
||||
AttachmentUtil.setData(myCtx, attachment, theBytes);
|
||||
}
|
||||
|
||||
|
||||
|
@ -271,8 +281,8 @@ public class BinaryAccessProvider {
|
|||
|
||||
// Path is Binary
|
||||
if (def.getName().equals("Binary")) {
|
||||
IBaseBinary binary = (IBaseBinary) type.get();
|
||||
return new IBinaryTarget() {
|
||||
IBaseBinary binary = (IBaseBinary) theElement;
|
||||
binaryTarget = new IBinaryTarget() {
|
||||
@Override
|
||||
public void setSize(Integer theSize) {
|
||||
// ignore
|
||||
|
@ -290,7 +300,7 @@ public class BinaryAccessProvider {
|
|||
|
||||
@Override
|
||||
public IBaseHasExtensions getTarget() {
|
||||
return (IBaseHasExtensions) BinaryUtil.getOrCreateData(myCtx, binary);
|
||||
return (IBaseHasExtensions) BinaryUtil.getOrCreateData(BinaryAccessProvider.this.myCtx, binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -308,9 +318,7 @@ public class BinaryAccessProvider {
|
|||
};
|
||||
}
|
||||
|
||||
String msg = myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownType", resType, thePath, def.getName());
|
||||
throw new InvalidRequestException(msg);
|
||||
|
||||
return Optional.ofNullable(binaryTarget);
|
||||
}
|
||||
|
||||
private String validateResourceTypeAndPath(@IdParam IIdType theResourceId, @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType<String> thePath) {
|
||||
|
@ -340,25 +348,5 @@ public class BinaryAccessProvider {
|
|||
return dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an Attachment datatype or Binary resource, since they both
|
||||
* hold binary content but don't look entirely similar
|
||||
*/
|
||||
private interface IBinaryTarget {
|
||||
|
||||
void setSize(Integer theSize);
|
||||
|
||||
String getContentType();
|
||||
|
||||
void setContentType(String theContentType);
|
||||
|
||||
byte[] getData();
|
||||
|
||||
void setData(byte[] theBytes);
|
||||
|
||||
IBaseHasExtensions getTarget();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -20,23 +20,37 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.IModelVisitor2;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.jpa.model.util.JpaConstants.EXT_EXTERNALIZED_BINARY_ID;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
@Interceptor
|
||||
public class BinaryStorageInterceptor {
|
||||
|
||||
|
@ -45,12 +59,39 @@ public class BinaryStorageInterceptor {
|
|||
private IBinaryStorageSvc myBinaryStorageSvc;
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
@Autowired
|
||||
private BinaryAccessProvider myBinaryAccessProvider;
|
||||
private Class<? extends IPrimitiveType<byte[]>> myBinaryType;
|
||||
private String myDeferredListKey;
|
||||
private long myAutoDeExternalizeMaximumBytes = 10 * FileUtils.ONE_MB;
|
||||
|
||||
/**
|
||||
* Any externalized binaries will be rehydrated if their size is below this thhreshold when
|
||||
* reading the resource back. Default is 10MB.
|
||||
*/
|
||||
public long getAutoDeExternalizeMaximumBytes() {
|
||||
return myAutoDeExternalizeMaximumBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any externalized binaries will be rehydrated if their size is below this thhreshold when
|
||||
* reading the resource back. Default is 10MB.
|
||||
*/
|
||||
public void setAutoDeExternalizeMaximumBytes(long theAutoDeExternalizeMaximumBytes) {
|
||||
myAutoDeExternalizeMaximumBytes = theAutoDeExternalizeMaximumBytes;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
myBinaryType = (Class<? extends IPrimitiveType<byte[]>>) myCtx.getElementDefinition("base64Binary").getImplementingClass();
|
||||
myDeferredListKey = getClass().getName() + "_" + hashCode() + "_DEFERRED_LIST";
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE)
|
||||
public void expungeResource(AtomicInteger theCounter, IBaseResource theResource) {
|
||||
|
||||
Class<? extends IBase> binaryType = myCtx.getElementDefinition("base64Binary").getImplementingClass();
|
||||
List<? extends IBase> binaryElements = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, binaryType);
|
||||
List<? extends IBase> binaryElements = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, myBinaryType);
|
||||
|
||||
List<String> attachmentIds = binaryElements
|
||||
.stream()
|
||||
|
@ -67,4 +108,215 @@ public class BinaryStorageInterceptor {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
|
||||
public void extractLargeBinariesBeforeCreate(ServletRequestDetails theRequestDetails, IBaseResource theResource, Pointcut thePointcut) throws IOException {
|
||||
extractLargeBinaries(theRequestDetails, theResource, thePointcut);
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
|
||||
public void extractLargeBinariesBeforeUpdate(ServletRequestDetails theRequestDetails, IBaseResource thePreviousResource, IBaseResource theResource, Pointcut thePointcut) throws IOException {
|
||||
blockIllegalExternalBinaryIds(thePreviousResource, theResource);
|
||||
extractLargeBinaries(theRequestDetails, theResource, thePointcut);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't allow clients to submit resources with binary storage attachments declared unless the ID was already in the
|
||||
* resource. In other words, only HAPI itself may add a binary storage ID extension to a resource unless that
|
||||
* extension was already present.
|
||||
*/
|
||||
private void blockIllegalExternalBinaryIds(IBaseResource thePreviousResource, IBaseResource theResource) {
|
||||
Set<String> existingBinaryIds = new HashSet<>();
|
||||
if (thePreviousResource != null) {
|
||||
List<? extends IPrimitiveType<byte[]>> base64fields = myCtx.newTerser().getAllPopulatedChildElementsOfType(thePreviousResource, myBinaryType);
|
||||
for (IPrimitiveType<byte[]> nextBase64 : base64fields) {
|
||||
if (nextBase64 instanceof IBaseHasExtensions) {
|
||||
((IBaseHasExtensions) nextBase64)
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t -> t.getUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED) == null)
|
||||
.filter(t -> EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
|
||||
.map(t -> (IPrimitiveType) t.getValue())
|
||||
.map(t -> t.getValueAsString())
|
||||
.filter(t -> isNotBlank(t))
|
||||
.forEach(t -> existingBinaryIds.add(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<? extends IPrimitiveType<byte[]>> base64fields = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, myBinaryType);
|
||||
for (IPrimitiveType<byte[]> nextBase64 : base64fields) {
|
||||
if (nextBase64 instanceof IBaseHasExtensions) {
|
||||
Optional<String> hasExternalizedBinaryReference = ((IBaseHasExtensions) nextBase64)
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t -> t.getUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED) == null)
|
||||
.filter(t -> t.getUrl().equals(EXT_EXTERNALIZED_BINARY_ID))
|
||||
.map(t->(IPrimitiveType) t.getValue())
|
||||
.map(t->t.getValueAsString())
|
||||
.filter(t->isNotBlank(t))
|
||||
.filter(t->{
|
||||
return !existingBinaryIds.contains(t);
|
||||
}).findFirst();
|
||||
|
||||
if (hasExternalizedBinaryReference.isPresent()) {
|
||||
String msg = myCtx.getLocalizer().getMessage(BaseHapiFhirDao.class, "externalizedBinaryStorageExtensionFoundInRequestBody", EXT_EXTERNALIZED_BINARY_ID, hasExternalizedBinaryReference.get());
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void extractLargeBinaries(ServletRequestDetails theRequestDetails, IBaseResource theResource, Pointcut thePointcut) throws IOException {
|
||||
if (theRequestDetails == null) {
|
||||
// RequestDetails will only be null for internal HAPI events. If externalization is required for them it will need to be done in a different way.
|
||||
return;
|
||||
}
|
||||
IIdType resourceId = theResource.getIdElement();
|
||||
if (!resourceId.hasResourceType() && resourceId.hasIdPart()) {
|
||||
String resourceType = myCtx.getResourceDefinition(theResource).getName();
|
||||
resourceId = new IdType(resourceType + "/" + resourceId.getIdPart());
|
||||
}
|
||||
|
||||
List<IBinaryTarget> attachments = recursivelyScanResourceForBinaryData(theResource);
|
||||
for (IBinaryTarget nextTarget : attachments) {
|
||||
byte[] data = nextTarget.getData();
|
||||
if (data != null && data.length > 0) {
|
||||
|
||||
long nextPayloadLength = data.length;
|
||||
String nextContentType = nextTarget.getContentType();
|
||||
boolean shouldStoreBlob = myBinaryStorageSvc.shouldStoreBlob(nextPayloadLength, resourceId, nextContentType);
|
||||
if (shouldStoreBlob) {
|
||||
|
||||
String newBlobId;
|
||||
if (resourceId.hasIdPart()) {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
|
||||
StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(resourceId, null, nextContentType, inputStream);
|
||||
newBlobId = storedDetails.getBlobId();
|
||||
} else {
|
||||
assert thePointcut == Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED : thePointcut.name();
|
||||
newBlobId = myBinaryStorageSvc.newBlobId();
|
||||
List<DeferredBinaryTarget> deferredBinaryTargets = getOrCreateDeferredBinaryStorageMap(theRequestDetails);
|
||||
DeferredBinaryTarget newDeferredBinaryTarget = new DeferredBinaryTarget(newBlobId, nextTarget, data);
|
||||
deferredBinaryTargets.add(newDeferredBinaryTarget);
|
||||
}
|
||||
|
||||
myBinaryAccessProvider.replaceDataWithExtension(nextTarget, newBlobId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<DeferredBinaryTarget> getOrCreateDeferredBinaryStorageMap(ServletRequestDetails theRequestDetails) {
|
||||
List<DeferredBinaryTarget> deferredBinaryTargets = (List<DeferredBinaryTarget>) theRequestDetails.getUserData().get(getDeferredListKey());
|
||||
if (deferredBinaryTargets == null) {
|
||||
deferredBinaryTargets = new ArrayList<>();
|
||||
theRequestDetails.getUserData().put(getDeferredListKey(), deferredBinaryTargets);
|
||||
}
|
||||
return deferredBinaryTargets;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED)
|
||||
public void storeLargeBinariesBeforeCreatePersistence(ServletRequestDetails theRequestDetails, IBaseResource theResource, Pointcut thePoincut) throws IOException {
|
||||
if (theRequestDetails == null) {
|
||||
return;
|
||||
}
|
||||
List<DeferredBinaryTarget> deferredBinaryTargets = (List<DeferredBinaryTarget>) theRequestDetails.getUserData().get(getDeferredListKey());
|
||||
if (deferredBinaryTargets != null) {
|
||||
IIdType resourceId = theResource.getIdElement();
|
||||
for (DeferredBinaryTarget next : deferredBinaryTargets) {
|
||||
String blobId = next.getBlobId();
|
||||
IBinaryTarget target = next.getBinaryTarget();
|
||||
InputStream dataStream = next.getDataStream();
|
||||
String contentType = target.getContentType();
|
||||
myBinaryStorageSvc.storeBlob(resourceId, blobId, contentType, dataStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getDeferredListKey() {
|
||||
return myDeferredListKey;
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESHOW_RESOURCES)
|
||||
public void preShow(IPreResourceShowDetails theDetails) throws IOException {
|
||||
long unmarshalledByteCount = 0;
|
||||
|
||||
for (IBaseResource nextResource : theDetails) {
|
||||
|
||||
IIdType resourceId = nextResource.getIdElement();
|
||||
List<IBinaryTarget> attachments = recursivelyScanResourceForBinaryData(nextResource);
|
||||
|
||||
for (IBinaryTarget nextTarget : attachments) {
|
||||
Optional<String> attachmentId = nextTarget.getAttachmentId();
|
||||
if (attachmentId.isPresent()) {
|
||||
|
||||
StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(resourceId, attachmentId.get());
|
||||
if (blobDetails == null) {
|
||||
String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "unknownBlobId");
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
|
||||
if ((unmarshalledByteCount + blobDetails.getBytes()) < myAutoDeExternalizeMaximumBytes) {
|
||||
|
||||
byte[] bytes = myBinaryStorageSvc.fetchBlob(resourceId, attachmentId.get());
|
||||
nextTarget.setData(bytes);
|
||||
unmarshalledByteCount += blobDetails.getBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private List<IBinaryTarget> recursivelyScanResourceForBinaryData(IBaseResource theResource) {
|
||||
List<IBinaryTarget> binaryTargets = new ArrayList<>();
|
||||
myCtx.newTerser().visit(theResource, new IModelVisitor2() {
|
||||
@Override
|
||||
public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
|
||||
|
||||
if (theElement.getClass().equals(myBinaryType)) {
|
||||
IBase parent = theContainingElementPath.get(theContainingElementPath.size() - 2);
|
||||
Optional<IBinaryTarget> binaryTarget = myBinaryAccessProvider.toBinaryTarget(parent);
|
||||
if (binaryTarget.isPresent()) {
|
||||
binaryTargets.add(binaryTarget.get());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return binaryTargets;
|
||||
}
|
||||
|
||||
private static class DeferredBinaryTarget {
|
||||
private final String myBlobId;
|
||||
private final IBinaryTarget myBinaryTarget;
|
||||
private final InputStream myDataStream;
|
||||
|
||||
private DeferredBinaryTarget(String theBlobId, IBinaryTarget theBinaryTarget, byte[] theData) {
|
||||
myBlobId = theBlobId;
|
||||
myBinaryTarget = theBinaryTarget;
|
||||
myDataStream = new ByteArrayInputStream(theData);
|
||||
}
|
||||
|
||||
String getBlobId() {
|
||||
return myBlobId;
|
||||
}
|
||||
|
||||
IBinaryTarget getBinaryTarget() {
|
||||
return myBinaryTarget;
|
||||
}
|
||||
|
||||
InputStream getDataStream() {
|
||||
return myDataStream;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
|
||||
import ca.uhn.fhir.jpa.dao.data.IBinaryStorageEntityDao;
|
||||
import ca.uhn.fhir.jpa.model.entity.BinaryStorageEntity;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import com.google.common.hash.HashingInputStream;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.CountingInputStream;
|
||||
|
@ -57,13 +58,13 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
|
||||
@Override
|
||||
@Transactional(Transactional.TxType.SUPPORTS)
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) {
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) {
|
||||
Date publishedDate = new Date();
|
||||
|
||||
HashingInputStream hashingInputStream = createHashingInputStream(theInputStream);
|
||||
CountingInputStream countingInputStream = createCountingInputStream(hashingInputStream);
|
||||
|
||||
String id = newRandomId();
|
||||
String id = super.provideIdForNewBlob(theBlobIdOrNull);
|
||||
|
||||
BinaryStorageEntity entity = new BinaryStorageEntity();
|
||||
entity.setResourceId(theResourceId.toUnqualifiedVersionless().getValue());
|
||||
|
@ -80,7 +81,7 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
txTemplate.execute(t->{
|
||||
txTemplate.execute(t -> {
|
||||
myEntityManager.persist(entity);
|
||||
return null;
|
||||
});
|
||||
|
@ -88,7 +89,7 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
// Update the entity with the final byte count and hash
|
||||
long bytes = countingInputStream.getCount();
|
||||
String hash = hashingInputStream.hash().toString();
|
||||
txTemplate.execute(t-> {
|
||||
txTemplate.execute(t -> {
|
||||
myBinaryStorageEntityDao.setSize(id, (int) bytes);
|
||||
myBinaryStorageEntityDao.setHash(id, hash);
|
||||
return null;
|
||||
|
@ -126,12 +127,7 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream inputStream = entityOpt.get().getBlob().getBinaryStream();
|
||||
IOUtils.copy(inputStream, theOutputStream);
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
copyBlobToOutputStream(theOutputStream, entityOpt.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -139,6 +135,32 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
@Override
|
||||
public void expungeBlob(IIdType theResourceId, String theBlobId) {
|
||||
Optional<BinaryStorageEntity> entityOpt = myBinaryStorageEntityDao.findByIdAndResourceId(theBlobId, theResourceId.toUnqualifiedVersionless().getValue());
|
||||
entityOpt.ifPresent(theBinaryStorageEntity -> myBinaryStorageEntityDao.delete(theBinaryStorageEntity));
|
||||
entityOpt.ifPresent(theBinaryStorageEntity -> myBinaryStorageEntityDao.deleteByPid(theBinaryStorageEntity.getBlobId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) throws IOException {
|
||||
BinaryStorageEntity entityOpt = myBinaryStorageEntityDao
|
||||
.findByIdAndResourceId(theBlobId, theResourceId.toUnqualifiedVersionless().getValue())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Unknown blob ID: " + theBlobId + " for resource ID " + theResourceId));
|
||||
|
||||
return copyBlobToByteArray(entityOpt);
|
||||
}
|
||||
|
||||
void copyBlobToOutputStream(OutputStream theOutputStream, BinaryStorageEntity theEntity) throws IOException {
|
||||
try (InputStream inputStream = theEntity.getBlob().getBinaryStream()) {
|
||||
IOUtils.copy(inputStream, theOutputStream);
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] copyBlobToByteArray(BinaryStorageEntity theEntity) throws IOException {
|
||||
int size = theEntity.getSize();
|
||||
try {
|
||||
return IOUtils.toByteArray(theEntity.getBlob().getBinaryStream(), size);
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
@ -31,6 +32,7 @@ import org.apache.commons.io.IOUtils;
|
|||
import org.apache.commons.io.input.CountingInputStream;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -63,8 +65,8 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException {
|
||||
String id = newRandomId();
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) throws IOException {
|
||||
String id = super.provideIdForNewBlob(theBlobIdOrNull);
|
||||
File storagePath = getStoragePath(id, true);
|
||||
|
||||
// Write binary file
|
||||
|
@ -111,17 +113,31 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
|
||||
@Override
|
||||
public boolean writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException {
|
||||
InputStream inputStream = getInputStream(theResourceId, theBlobId);
|
||||
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
IOUtils.copy(inputStream, theOutputStream);
|
||||
theOutputStream.close();
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private InputStream getInputStream(IIdType theResourceId, String theBlobId) throws FileNotFoundException {
|
||||
File storagePath = getStoragePath(theBlobId, false);
|
||||
InputStream inputStream = null;
|
||||
if (storagePath != null) {
|
||||
File file = getStorageFilename(storagePath, theResourceId, theBlobId);
|
||||
if (file.exists()) {
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
IOUtils.copy(inputStream, theOutputStream);
|
||||
theOutputStream.close();
|
||||
}
|
||||
inputStream = new FileInputStream(file);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -139,6 +155,20 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) throws IOException {
|
||||
StoredDetails details = fetchBlobDetails(theResourceId, theBlobId);
|
||||
try (InputStream inputStream = getInputStream(theResourceId, theBlobId)) {
|
||||
|
||||
if (inputStream != null) {
|
||||
return IOUtils.toByteArray(inputStream, details.getBytes());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new ResourceNotFoundException("Unknown blob ID: " + theBlobId + " for resource ID " + theResourceId);
|
||||
}
|
||||
|
||||
private void delete(File theStorageFile, String theBlobId) {
|
||||
Validate.isTrue(theStorageFile.delete(), "Failed to delete file for blob %s", theBlobId);
|
||||
}
|
||||
|
@ -164,7 +194,7 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
private File getStoragePath(String theId, boolean theCreate) {
|
||||
File path = myBasePath;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
path = new File(path, theId.substring(i, i+1));
|
||||
path = new File(path, theId.substring(i, i + 1));
|
||||
if (!path.exists()) {
|
||||
if (theCreate) {
|
||||
mkdir(path);
|
||||
|
|
|
@ -20,22 +20,12 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
* #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 com.google.common.hash.HashingInputStream;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
|
||||
public interface IBinaryStorageSvc {
|
||||
|
||||
|
@ -77,15 +67,22 @@ public interface IBinaryStorageSvc {
|
|||
*/
|
||||
boolean shouldStoreBlob(long theSize, IIdType theResourceId, String theContentType);
|
||||
|
||||
/**
|
||||
* Generate a new blob ID that will be passed to {@link #storeBlob(IIdType, String, String, InputStream)} later
|
||||
*/
|
||||
String newBlobId();
|
||||
|
||||
/**
|
||||
* Store a new binary blob
|
||||
*
|
||||
* @param theResourceId The resource ID that owns this blob. Note that it should not be possible to retrieve a blob without both the resource ID and the blob ID being correct.
|
||||
* @param theContentType The content type to associate with this blob
|
||||
* @param theInputStream An InputStream to read from. This method should close the stream when it has been fully consumed.
|
||||
* @param theResourceId The resource ID that owns this blob. Note that it should not be possible to retrieve a blob without both the resource ID and the blob ID being correct.
|
||||
* @param theBlobIdOrNull If set, forces
|
||||
* @param theContentType The content type to associate with this blob
|
||||
* @param theInputStream An InputStream to read from. This method should close the stream when it has been fully consumed.
|
||||
* @return Returns details about the stored data
|
||||
*/
|
||||
StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException;
|
||||
@Nonnull
|
||||
StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) throws IOException;
|
||||
|
||||
StoredDetails fetchBlobDetails(IIdType theResourceId, String theBlobId) throws IOException;
|
||||
|
||||
|
@ -96,100 +93,12 @@ public interface IBinaryStorageSvc {
|
|||
|
||||
void expungeBlob(IIdType theResourceId, String theBlobId);
|
||||
|
||||
|
||||
@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)
|
||||
class StoredDetails {
|
||||
|
||||
@JsonProperty("blobId")
|
||||
private String myBlobId;
|
||||
@JsonProperty("bytes")
|
||||
private long myBytes;
|
||||
@JsonProperty("contentType")
|
||||
private String myContentType;
|
||||
@JsonProperty("hash")
|
||||
private String myHash;
|
||||
@JsonProperty("published")
|
||||
@JsonSerialize(using = JsonDateSerializer.class)
|
||||
@JsonDeserialize(using = JsonDateDeserializer.class)
|
||||
private Date myPublished;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public StoredDetails() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public StoredDetails(@Nonnull String theBlobId, long theBytes, @Nonnull String theContentType, HashingInputStream theIs, Date thePublished) {
|
||||
myBlobId = theBlobId;
|
||||
myBytes = theBytes;
|
||||
myContentType = theContentType;
|
||||
myHash = theIs.hash().toString();
|
||||
myPublished = thePublished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("blobId", myBlobId)
|
||||
.append("bytes", myBytes)
|
||||
.append("contentType", myContentType)
|
||||
.append("hash", myHash)
|
||||
.append("published", myPublished)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return myHash;
|
||||
}
|
||||
|
||||
public StoredDetails setHash(String theHash) {
|
||||
myHash = theHash;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getPublished() {
|
||||
return myPublished;
|
||||
}
|
||||
|
||||
public StoredDetails setPublished(Date thePublished) {
|
||||
myPublished = thePublished;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getContentType() {
|
||||
return myContentType;
|
||||
}
|
||||
|
||||
public StoredDetails setContentType(String theContentType) {
|
||||
myContentType = theContentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getBlobId() {
|
||||
return myBlobId;
|
||||
}
|
||||
|
||||
public StoredDetails setBlobId(String theBlobId) {
|
||||
myBlobId = theBlobId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getBytes() {
|
||||
return myBytes;
|
||||
}
|
||||
|
||||
public StoredDetails setBytes(long theBytes) {
|
||||
myBytes = theBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Fetch the contents of the given blob
|
||||
*
|
||||
* @param theResourceId The resource ID
|
||||
* @param theBlobId The blob ID
|
||||
* @return The payload as a byte array
|
||||
*/
|
||||
byte[] fetchBlob(IIdType theResourceId, String theBlobId) throws IOException;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package ca.uhn.fhir.jpa.binstore;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 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.model.util.JpaConstants;
|
||||
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/**
|
||||
* Wraps an Attachment datatype or Binary resource, since they both
|
||||
* hold binary content but don't look entirely similar
|
||||
*/
|
||||
interface IBinaryTarget {
|
||||
|
||||
void setSize(Integer theSize);
|
||||
|
||||
String getContentType();
|
||||
|
||||
void setContentType(String theContentType);
|
||||
|
||||
byte[] getData();
|
||||
|
||||
void setData(byte[] theBytes);
|
||||
|
||||
IBaseHasExtensions getTarget();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default Optional<String> getAttachmentId() {
|
||||
return getTarget()
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
|
||||
.filter(t -> t.getValue() instanceof IPrimitiveType)
|
||||
.map(t -> (IPrimitiveType<String>) t.getValue())
|
||||
.map(t -> t.getValue())
|
||||
.filter(t -> isNotBlank(t))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
|
@ -49,8 +49,8 @@ public class MemoryBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl impleme
|
|||
}
|
||||
|
||||
@Override
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException {
|
||||
String id = newRandomId();
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) throws IOException {
|
||||
String id = super.provideIdForNewBlob(theBlobIdOrNull);
|
||||
String key = toKey(theResourceId, id);
|
||||
|
||||
HashingInputStream hashingIs = createHashingInputStream(theInputStream);
|
||||
|
@ -88,8 +88,18 @@ public class MemoryBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl impleme
|
|||
myDetailsMap.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) {
|
||||
String key = toKey(theResourceId, theBlobId);
|
||||
return myDataMap.get(key);
|
||||
}
|
||||
|
||||
private String toKey(IIdType theResourceId, String theBlobId) {
|
||||
return theBlobId + '-' + theResourceId.toUnqualifiedVersionless().getValue();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
myDetailsMap.clear();
|
||||
myDataMap.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,12 @@ public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
}
|
||||
|
||||
@Override
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) {
|
||||
public String newBlobId() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
@ -71,4 +76,9 @@ public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
public void expungeBlob(IIdType theIdElement, String theBlobId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
package ca.uhn.fhir.jpa.binstore;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 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 com.google.common.hash.HashingInputStream;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Date;
|
||||
|
||||
@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 StoredDetails {
|
||||
|
||||
@JsonProperty("blobId")
|
||||
private String myBlobId;
|
||||
@JsonProperty("bytes")
|
||||
private long myBytes;
|
||||
@JsonProperty("contentType")
|
||||
private String myContentType;
|
||||
@JsonProperty("hash")
|
||||
private String myHash;
|
||||
@JsonProperty("published")
|
||||
@JsonSerialize(using = JsonDateSerializer.class)
|
||||
@JsonDeserialize(using = JsonDateDeserializer.class)
|
||||
private Date myPublished;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public StoredDetails() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public StoredDetails(@Nonnull String theBlobId, long theBytes, @Nonnull String theContentType, HashingInputStream theIs, Date thePublished) {
|
||||
myBlobId = theBlobId;
|
||||
myBytes = theBytes;
|
||||
myContentType = theContentType;
|
||||
myHash = theIs.hash().toString();
|
||||
myPublished = thePublished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("blobId", myBlobId)
|
||||
.append("bytes", myBytes)
|
||||
.append("contentType", myContentType)
|
||||
.append("hash", myHash)
|
||||
.append("published", myPublished)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return myHash;
|
||||
}
|
||||
|
||||
public StoredDetails setHash(String theHash) {
|
||||
myHash = theHash;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getPublished() {
|
||||
return myPublished;
|
||||
}
|
||||
|
||||
public StoredDetails setPublished(Date thePublished) {
|
||||
myPublished = thePublished;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getContentType() {
|
||||
return myContentType;
|
||||
}
|
||||
|
||||
public StoredDetails setContentType(String theContentType) {
|
||||
myContentType = theContentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getBlobId() {
|
||||
return myBlobId;
|
||||
}
|
||||
|
||||
public StoredDetails setBlobId(String theBlobId) {
|
||||
myBlobId = theBlobId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getBytes() {
|
||||
return myBytes;
|
||||
}
|
||||
|
||||
public StoredDetails setBytes(long theBytes) {
|
||||
myBytes = theBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -165,7 +165,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
|
|||
|
||||
if (jobToDelete.isPresent()) {
|
||||
|
||||
ourLog.info("Deleting bulk export job: {}", jobToDelete.get().getJobId());
|
||||
ourLog.info("Deleting bulk export job: {}", jobToDelete.get());
|
||||
|
||||
myTxTemplate.execute(t -> {
|
||||
|
||||
|
@ -176,17 +176,20 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
|
|||
ourLog.info("Purging bulk data file: {}", nextFile.getResourceId());
|
||||
getBinaryDao().delete(toId(nextFile.getResourceId()));
|
||||
getBinaryDao().forceExpungeInExistingTransaction(toId(nextFile.getResourceId()), new ExpungeOptions().setExpungeDeletedResources(true).setExpungeOldVersions(true), null);
|
||||
myBulkExportCollectionFileDao.delete(nextFile);
|
||||
myBulkExportCollectionFileDao.deleteByPid(nextFile.getId());
|
||||
|
||||
}
|
||||
|
||||
myBulkExportCollectionDao.delete(nextCollection);
|
||||
myBulkExportCollectionDao.deleteByPid(nextCollection.getId());
|
||||
}
|
||||
|
||||
myBulkExportJobDao.delete(job);
|
||||
ourLog.info("*** ABOUT TO DELETE");
|
||||
myBulkExportJobDao.deleteByPid(job.getId());
|
||||
return null;
|
||||
});
|
||||
|
||||
ourLog.info("Finished deleting bulk export job: {}", jobToDelete.get());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -452,11 +455,17 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Transactional(Transactional.TxType.NEVER)
|
||||
public synchronized void cancelAndPurgeAllJobs() {
|
||||
myBulkExportCollectionFileDao.deleteAll();
|
||||
myBulkExportCollectionDao.deleteAll();
|
||||
myBulkExportJobDao.deleteAll();
|
||||
myTxTemplate.execute(t -> {
|
||||
ourLog.info("Deleting all files");
|
||||
myBulkExportCollectionFileDao.deleteAllFiles();
|
||||
ourLog.info("Deleting all collections");
|
||||
myBulkExportCollectionDao.deleteAllFiles();
|
||||
ourLog.info("Deleting all jobs");
|
||||
myBulkExportJobDao.deleteAllFiles();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@DisallowConcurrentExecution
|
||||
|
|
|
@ -32,6 +32,13 @@ import ca.uhn.fhir.jpa.subscription.module.cache.LinkedBlockingQueueSubscribable
|
|||
import ca.uhn.fhir.jpa.subscription.module.channel.ISubscribableChannelFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
||||
import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
|
||||
import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
|
||||
import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
|
||||
import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
|
||||
|
@ -256,7 +263,6 @@ public abstract class BaseConfig {
|
|||
}
|
||||
|
||||
|
||||
|
||||
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {
|
||||
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
|
||||
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package ca.uhn.fhir.jpa.config;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 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.term.TermCodeSystemStorageSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public abstract class BaseConfigDstu3Plus extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
public ITermCodeSystemStorageSvc termCodeSystemStorageSvc() {
|
||||
return new TermCodeSystemStorageSvcImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ITermDeferredStorageSvc termDeferredStorageSvc() {
|
||||
return new TermDeferredStorageSvcImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ITermReindexingSvc termReindexingSvc() {
|
||||
return new TermReindexingSvcImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public abstract ITermVersionAdapterSvc terminologyVersionAdapterSvc();
|
||||
|
||||
}
|
|
@ -7,8 +7,10 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
|||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu2;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.jpa.term.TermReadSvcDstu2;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
|
||||
import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcDstu2;
|
||||
import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
|
@ -134,8 +136,8 @@ public class BaseDstu2Config extends BaseConfig {
|
|||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IHapiTerminologySvc terminologyService() {
|
||||
return new HapiTerminologySvcDstu2();
|
||||
public ITermReadSvc terminologyService() {
|
||||
return new TermReadSvcDstu2();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.config.dstu3;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus;
|
||||
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
|
@ -12,10 +13,12 @@ import ca.uhn.fhir.jpa.provider.GraphQLProvider;
|
|||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.TermReadSvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
|
||||
import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
|
@ -26,7 +29,6 @@ import org.hl7.fhir.dstu3.hapi.validation.CachingValidationSupport;
|
|||
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -55,13 +57,19 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class BaseDstu3Config extends BaseConfig {
|
||||
public class BaseDstu3Config extends BaseConfigDstu3Plus {
|
||||
|
||||
@Override
|
||||
public FhirContext fhirContext() {
|
||||
return fhirContextDstu3();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public ITermVersionAdapterSvc terminologyVersionAdapterSvc() {
|
||||
return new TermVersionAdapterSvcDstu3();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public FhirContext fhirContextDstu3() {
|
||||
|
@ -109,10 +117,9 @@ public class BaseDstu3Config extends BaseConfig {
|
|||
return new JpaValidationSupportChainDstu3();
|
||||
}
|
||||
|
||||
@Bean(name = "myJpaValidationSupportDstu3", autowire = Autowire.BY_NAME)
|
||||
@Bean(name = "myJpaValidationSupportDstu3")
|
||||
public ca.uhn.fhir.jpa.dao.dstu3.IJpaValidationSupportDstu3 jpaValidationSupportDstu3() {
|
||||
ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3 retVal = new ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3();
|
||||
return retVal;
|
||||
return new ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3();
|
||||
}
|
||||
|
||||
@Bean(name = "myResourceCountsCache")
|
||||
|
@ -122,13 +129,12 @@ public class BaseDstu3Config extends BaseConfig {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
@Bean
|
||||
public IFulltextSearchSvc searchDaoDstu3() {
|
||||
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();
|
||||
return searchDao;
|
||||
return new FulltextSearchSvcImpl();
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
@Bean
|
||||
public SearchParamExtractorDstu3 searchParamExtractor() {
|
||||
return new SearchParamExtractorDstu3();
|
||||
}
|
||||
|
@ -138,10 +144,9 @@ public class BaseDstu3Config extends BaseConfig {
|
|||
return new SearchParamRegistryDstu3();
|
||||
}
|
||||
|
||||
@Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME)
|
||||
@Bean(name = "mySystemDaoDstu3")
|
||||
public IFhirSystemDao<org.hl7.fhir.dstu3.model.Bundle, org.hl7.fhir.dstu3.model.Meta> systemDaoDstu3() {
|
||||
ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3 retVal = new ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3();
|
||||
return retVal;
|
||||
return new ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3();
|
||||
}
|
||||
|
||||
@Bean(name = "mySystemProviderDstu3")
|
||||
|
@ -152,18 +157,18 @@ public class BaseDstu3Config extends BaseConfig {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IHapiTerminologyLoaderSvc terminologyLoaderService() {
|
||||
return new TerminologyLoaderSvcImpl();
|
||||
@Bean
|
||||
public ITermLoaderSvc termLoaderService() {
|
||||
return new TermLoaderSvcImpl();
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IHapiTerminologySvcDstu3 terminologyService() {
|
||||
return new HapiTerminologySvcDstu3();
|
||||
@Bean
|
||||
public ITermReadSvcDstu3 terminologyService() {
|
||||
return new TermReadSvcDstu3();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu3")
|
||||
@Bean(name = "myJpaValidationSupportChainDstu3")
|
||||
public IValidationSupport validationSupportChainDstu3() {
|
||||
return new CachingValidationSupport(jpaValidationSupportChain());
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.config.r4;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus;
|
||||
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
|
@ -12,10 +13,10 @@ import ca.uhn.fhir.jpa.provider.GraphQLProvider;
|
|||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4;
|
||||
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.*;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
|
||||
import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
|
@ -55,13 +56,19 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class BaseR4Config extends BaseConfig {
|
||||
public class BaseR4Config extends BaseConfigDstu3Plus {
|
||||
|
||||
@Override
|
||||
public FhirContext fhirContext() {
|
||||
return fhirContextR4();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public ITermVersionAdapterSvc terminologyVersionAdapterSvc() {
|
||||
return new TermVersionAdapterSvcR4();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public FhirContext fhirContextR4() {
|
||||
|
@ -154,13 +161,13 @@ public class BaseR4Config extends BaseConfig {
|
|||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IHapiTerminologyLoaderSvc terminologyLoaderService() {
|
||||
return new TerminologyLoaderSvcImpl();
|
||||
public ITermLoaderSvc termLoaderService() {
|
||||
return new TermLoaderSvcImpl();
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IHapiTerminologySvcR4 terminologyService() {
|
||||
return new HapiTerminologySvcR4();
|
||||
public ITermReadSvcR4 terminologyService() {
|
||||
return new TermReadSvcR4();
|
||||
}
|
||||
|
||||
@Primary
|
||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.config.r5;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus;
|
||||
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
|
@ -12,10 +13,10 @@ import ca.uhn.fhir.jpa.provider.GraphQLProvider;
|
|||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR5;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR5;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR5;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR5;
|
||||
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.*;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
|
||||
import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR5;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
|
@ -55,13 +56,19 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class BaseR5Config extends BaseConfig {
|
||||
public class BaseR5Config extends BaseConfigDstu3Plus {
|
||||
|
||||
@Override
|
||||
public FhirContext fhirContext() {
|
||||
return fhirContextR5();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public ITermVersionAdapterSvc terminologyVersionAdapterSvc() {
|
||||
return new TermVersionAdapterSvcR5();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public FhirContext fhirContextR5() {
|
||||
|
@ -154,13 +161,13 @@ public class BaseR5Config extends BaseConfig {
|
|||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IHapiTerminologyLoaderSvc terminologyLoaderService() {
|
||||
return new TerminologyLoaderSvcImpl();
|
||||
public ITermLoaderSvc terminologyLoaderService() {
|
||||
return new TermLoaderSvcImpl();
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IHapiTerminologySvcR5 terminologyService() {
|
||||
return new HapiTerminologySvcR5();
|
||||
public ITermReadSvcR5 terminologyService() {
|
||||
return new TermReadSvcR5();
|
||||
}
|
||||
|
||||
@Primary
|
||||
|
|
|
@ -20,13 +20,12 @@ import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
|||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||
import ca.uhn.fhir.jpa.util.AddRemoveCount;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
|
@ -53,7 +52,6 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetai
|
|||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.CoverageIgnore;
|
||||
import ca.uhn.fhir.util.MetaUtil;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import ca.uhn.fhir.util.XmlUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Charsets;
|
||||
|
@ -62,8 +60,6 @@ import com.google.common.hash.HashFunction;
|
|||
import com.google.common.hash.Hashing;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.internal.SessionImpl;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -81,7 +77,6 @@ import javax.annotation.PostConstruct;
|
|||
import javax.persistence.*;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.xml.stream.events.Characters;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
|
@ -139,7 +134,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
@Autowired
|
||||
protected ISearchParamRegistry mySerarchParamRegistry;
|
||||
@Autowired
|
||||
protected IHapiTerminologySvc myTerminologySvc;
|
||||
protected ITermReadSvc myTerminologySvc;
|
||||
@Autowired
|
||||
protected IResourceHistoryTableDao myResourceHistoryTableDao;
|
||||
@Autowired
|
||||
|
@ -161,8 +156,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
@Autowired
|
||||
private ISearchCacheSvc mySearchCacheSvc;
|
||||
@Autowired
|
||||
private ISearchResultCacheSvc mySearchResultCacheSvc;
|
||||
@Autowired
|
||||
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
|
@ -192,20 +185,18 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
* none was created, returns null.
|
||||
*/
|
||||
protected ForcedId createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId, boolean theCreateForPureNumericIds) {
|
||||
ForcedId retVal = null;
|
||||
if (theId.isEmpty() == false && theId.hasIdPart() && theEntity.getForcedId() == null) {
|
||||
if (!theCreateForPureNumericIds && IdHelperService.isValidPid(theId)) {
|
||||
return null;
|
||||
if (theCreateForPureNumericIds || !IdHelperService.isValidPid(theId)) {
|
||||
retVal = new ForcedId();
|
||||
retVal.setResourceType(theEntity.getResourceType());
|
||||
retVal.setForcedId(theId.getIdPart());
|
||||
retVal.setResource(theEntity);
|
||||
theEntity.setForcedId(retVal);
|
||||
}
|
||||
|
||||
ForcedId fid = new ForcedId();
|
||||
fid.setResourceType(theEntity.getResourceType());
|
||||
fid.setForcedId(theId.getIdPart());
|
||||
fid.setResource(theEntity);
|
||||
theEntity.setForcedId(fid);
|
||||
return fid;
|
||||
}
|
||||
|
||||
return null;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set<ResourceTag> allDefs) {
|
||||
|
@ -285,39 +276,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
|
||||
}
|
||||
|
||||
private void findMatchingTagIds(RequestDetails theRequest, String theResourceName, IIdType theResourceId, Set<Long> tagIds, Class<? extends BaseTag> entityClass) {
|
||||
{
|
||||
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
|
||||
Root<? extends BaseTag> from = cq.from(entityClass);
|
||||
cq.multiselect(from.get("myTagId").as(Long.class)).distinct(true);
|
||||
|
||||
if (theResourceName != null) {
|
||||
Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName);
|
||||
if (theResourceId != null) {
|
||||
cq.where(typePredicate, builder.equal(from.get("myResourceId"), myIdHelperService.translateForcedIdToPid(theResourceName, theResourceId.getIdPart(), theRequest)));
|
||||
} else {
|
||||
cq.where(typePredicate);
|
||||
}
|
||||
}
|
||||
|
||||
TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
|
||||
for (Tuple next : query.getResultList()) {
|
||||
tagIds.add(next.get(0, Long.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void flushJpaSession() {
|
||||
SessionImpl session = (SessionImpl) myEntityManager.unwrap(Session.class);
|
||||
int insertionCount = session.getActionQueue().numberOfInsertions();
|
||||
int updateCount = session.getActionQueue().numberOfUpdates();
|
||||
|
||||
StopWatch sw = new StopWatch();
|
||||
myEntityManager.flush();
|
||||
ourLog.debug("Session flush took {}ms for {} inserts and {} updates", sw.getMillis(), insertionCount, updateCount);
|
||||
}
|
||||
|
||||
private Set<ResourceTag> getAllTagDefinitions(ResourceTable theEntity) {
|
||||
HashSet<ResourceTag> retVal = Sets.newHashSet();
|
||||
if (theEntity.isHasTags()) {
|
||||
|
@ -358,7 +316,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
|
||||
return myDaoRegistry.getResourceDaoOrNull(theType);
|
||||
}
|
||||
|
@ -446,6 +403,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
newVersion = Long.toString(newVersionLong);
|
||||
}
|
||||
|
||||
assert theResourceId != null;
|
||||
IIdType newId = theResourceId.withVersion(newVersion);
|
||||
theResource.getIdElement().setValue(newId.getValue());
|
||||
theSavedEntity.setVersion(newVersionLong);
|
||||
|
@ -821,7 +779,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
byte[] resourceBytes;
|
||||
ResourceEncodingEnum resourceEncoding;
|
||||
Collection<? extends BaseTag> myTagList;
|
||||
Long version;
|
||||
long version;
|
||||
String provenanceSourceUri = null;
|
||||
String provenanceRequestId = null;
|
||||
|
||||
|
@ -941,15 +899,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
+ (isNotBlank(provenanceRequestId) ? "#" : "")
|
||||
+ defaultString(provenanceRequestId);
|
||||
|
||||
if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
|
||||
IBaseExtension<?, ?> sourceExtension = ((IBaseHasExtensions) retVal.getMeta()).addExtension();
|
||||
sourceExtension.setUrl(JpaConstants.EXT_META_SOURCE);
|
||||
IPrimitiveType<String> value = (IPrimitiveType<String>) myContext.getElementDefinition("uri").newInstance();
|
||||
value.setValue(sourceString);
|
||||
sourceExtension.setValue(value);
|
||||
} else if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
|
||||
MetaUtil.setSource(myContext, retVal.getMeta(), sourceString);
|
||||
}
|
||||
MetaUtil.setSource(myContext, retVal, sourceString);
|
||||
|
||||
}
|
||||
|
||||
return retVal;
|
||||
|
@ -1119,7 +1070,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
source = ((IBaseHasExtensions) theResource.getMeta())
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t -> JpaConstants.EXT_META_SOURCE.equals(t.getUrl()))
|
||||
.filter(t -> Constants.EXT_META_SOURCE.equals(t.getUrl()))
|
||||
.filter(t -> t.getValue() instanceof IPrimitiveType)
|
||||
.map(t -> ((IPrimitiveType) t.getValue()).getValueAsString())
|
||||
.findFirst()
|
||||
|
|
|
@ -55,8 +55,6 @@ import ca.uhn.fhir.validation.*;
|
|||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
import org.hl7.fhir.r4.model.InstantType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
@ -97,8 +95,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
private String myResourceName;
|
||||
private Class<T> myResourceType;
|
||||
private String mySecondaryPrimaryKeyParamName;
|
||||
private Class<? extends IPrimitiveType<byte[]>> myBase64Type;
|
||||
|
||||
@Override
|
||||
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) {
|
||||
|
@ -193,9 +189,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@Override
|
||||
public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequest) {
|
||||
if (theId == null || !theId.hasIdPart()) {
|
||||
throw new InvalidRequestException("Can not perform delete, no ID provided");
|
||||
}
|
||||
validateIdPresentForDelete(theId);
|
||||
|
||||
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
|
||||
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
|
||||
|
@ -271,8 +266,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@Override
|
||||
public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) {
|
||||
validateIdPresentForDelete(theId);
|
||||
|
||||
DeleteConflictList deleteConflicts = new DeleteConflictList();
|
||||
if (theId != null && isNotBlank(theId.getValue())) {
|
||||
if (isNotBlank(theId.getValue())) {
|
||||
deleteConflicts.setResourceIdMarkedForDeletion(theId);
|
||||
}
|
||||
|
||||
|
@ -376,6 +373,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return outcome;
|
||||
}
|
||||
|
||||
private void validateIdPresentForDelete(IIdType theId) {
|
||||
if (theId == null || !theId.hasIdPart()) {
|
||||
throw new InvalidRequestException("Can not perform delete, no ID provided");
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void detectSearchDaoDisabled() {
|
||||
if (mySearchDao != null && mySearchDao.isDisabled()) {
|
||||
|
@ -554,12 +557,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
myEntityManager.merge(entity);
|
||||
}
|
||||
|
||||
private void validateExpungeEnabled() {
|
||||
if (!myDaoConfig.isExpungeEnabled()) {
|
||||
throw new MethodNotAllowedException("$expunge is not enabled on this server");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateExpungeEnabled() {
|
||||
if (!myDaoConfig.isExpungeEnabled()) {
|
||||
throw new MethodNotAllowedException("$expunge is not enabled on this server");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
public ExpungeOutcome expunge(IIdType theId, ExpungeOptions theExpungeOptions, RequestDetails theRequest) {
|
||||
|
@ -573,9 +576,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
|
||||
|
||||
BaseHasResource entity = txTemplate.execute(t -> readEntity(theId, theRequest));
|
||||
Validate.notNull(entity, "Resource with ID %s not found in database", theId);
|
||||
|
||||
if (theId.hasVersionIdPart()) {
|
||||
BaseHasResource currentVersion;
|
||||
currentVersion = txTemplate.execute(t -> readEntity(theId.toVersionless(), theRequest));
|
||||
Validate.notNull(currentVersion, "Current version of resource with ID %s not found in database", theId.toVersionless());
|
||||
|
||||
if (entity.getVersion() == currentVersion.getVersion()) {
|
||||
throw new PreconditionFailedException("Can not perform version-specific expunge of resource " + theId.toUnqualified().getValue() + " as this is the current version");
|
||||
}
|
||||
|
@ -815,21 +822,17 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return update(destinationCasted, null, true, theRequest);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
@Override
|
||||
public void start() {
|
||||
ourLog.debug("Starting resource DAO for type: {}", getResourceName());
|
||||
super.start();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType);
|
||||
myResourceName = def.getName();
|
||||
|
||||
if (mySecondaryPrimaryKeyParamName != null) {
|
||||
RuntimeSearchParam sp = mySearchParamRegistry.getSearchParamByName(def, mySecondaryPrimaryKeyParamName);
|
||||
if (sp == null) {
|
||||
throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]");
|
||||
}
|
||||
if (sp.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
|
||||
throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "] is not a token type, only token is supported");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -874,25 +877,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't allow clients to submit resources with binary storage attachments present.
|
||||
*/
|
||||
List<? extends IPrimitiveType<byte[]>> base64fields = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, myBase64Type);
|
||||
for (IPrimitiveType<byte[]> nextBase64 : base64fields) {
|
||||
if (nextBase64 instanceof IBaseHasExtensions) {
|
||||
boolean hasExternalizedBinaryReference = ((IBaseHasExtensions) nextBase64)
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t-> t.getUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED) == null)
|
||||
.anyMatch(t-> t.getUrl().equals(EXT_EXTERNALIZED_BINARY_ID));
|
||||
if (hasExternalizedBinaryReference) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "externalizedBinaryStorageExtensionFoundInRequestBody", EXT_EXTERNALIZED_BINARY_ID);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1188,21 +1172,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to share the same value.
|
||||
*/
|
||||
public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) {
|
||||
mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
super.start();
|
||||
ourLog.debug("Starting resource DAO for type: {}", getResourceName());
|
||||
myBase64Type = (Class<? extends IPrimitiveType<byte[]>>) getContext().getElementDefinition("base64Binary").getImplementingClass();
|
||||
}
|
||||
|
||||
protected <MT extends IBaseMetaType> MT toMetaDt(Class<MT> theType, Collection<TagDefinition> tagDefinitions) {
|
||||
MT retVal;
|
||||
try {
|
||||
|
@ -1317,12 +1286,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(resourceDef, qualifiedParamName.getParamName());
|
||||
|
||||
for (String nextValue : theSource.get(nextParamName)) {
|
||||
if (isNotBlank(nextValue)) {
|
||||
QualifiedParamList qualifiedParam = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifiedParamName.getWholeQualifier(), nextValue);
|
||||
List<QualifiedParamList> paramList = Collections.singletonList(qualifiedParam);
|
||||
IQueryParameterAnd<?> parsedParam = ParameterUtil.parseQueryParams(getContext(), paramDef, nextParamName, paramList);
|
||||
theTarget.add(qualifiedParamName.getParamName(), parsedParam);
|
||||
}
|
||||
QualifiedParamList qualifiedParam = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifiedParamName.getWholeQualifier(), nextValue);
|
||||
List<QualifiedParamList> paramList = Collections.singletonList(qualifiedParam);
|
||||
IQueryParameterAnd<?> parsedParam = ParameterUtil.parseQueryParams(getContext(), paramDef, nextParamName, paramList);
|
||||
theTarget.add(qualifiedParamName.getParamName(), parsedParam);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1343,6 +1310,16 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return update(theResource, theMatchUrl, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome update(T theResource, String theMatchUrl, RequestDetails theRequestDetails) {
|
||||
return update(theResource, theMatchUrl, true, theRequestDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, RequestDetails theRequestDetails) {
|
||||
return update(theResource, theMatchUrl, thePerformIndexing, false, theRequestDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequest) {
|
||||
if (theResource == null) {
|
||||
|
@ -1439,16 +1416,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return outcome;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome update(T theResource, String theMatchUrl, RequestDetails theRequestDetails) {
|
||||
return update(theResource, theMatchUrl, true, theRequestDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, RequestDetails theRequestDetails) {
|
||||
return update(theResource, theMatchUrl, thePerformIndexing, false, theRequestDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
|
||||
if (theRequest != null) {
|
||||
|
@ -1500,11 +1467,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
} else if (isNotBlank(theRawResource)) {
|
||||
result = validator.validateWithResult(theRawResource, options);
|
||||
} else if (theResource != null) {
|
||||
result = validator.validateWithResult(theResource, options);
|
||||
} else {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "cantValidateWithNoResource");
|
||||
throw new InvalidRequestException(msg);
|
||||
result = validator.validateWithResult(theResource, options);
|
||||
}
|
||||
|
||||
if (result.isSuccessful()) {
|
||||
|
@ -1519,9 +1483,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
/**
|
||||
* Get the resource definition from the criteria which specifies the resource type
|
||||
*
|
||||
* @param criteria
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(String criteria) {
|
||||
|
|
|
@ -74,6 +74,10 @@ public class DaoConfig {
|
|||
)));
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(DaoConfig.class);
|
||||
private static final int DEFAULT_EXPUNGE_BATCH_SIZE = 800;
|
||||
|
||||
// update setter javadoc if default changes
|
||||
public static final int DEFAULT_MAX_EXPANSION_SIZE = 1000;
|
||||
|
||||
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
|
||||
|
||||
/**
|
||||
|
@ -115,10 +119,7 @@ public class DaoConfig {
|
|||
* update setter javadoc if default changes
|
||||
*/
|
||||
private boolean myIndexContainedResources = true;
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private int myMaximumExpansionSize = 5000;
|
||||
private int myMaximumExpansionSize = DEFAULT_MAX_EXPANSION_SIZE;
|
||||
private Integer myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION;
|
||||
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
|
||||
/**
|
||||
|
@ -564,8 +565,12 @@ public class DaoConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of codes that will be added to a valueset expansion before
|
||||
* the operation will be failed as too costly
|
||||
* Sets the maximum number of codes that will be added to an in-memory valueset expansion before
|
||||
* the operation will be failed as too costly. Note that this setting applies only to
|
||||
* in-memory expansions and does not apply to expansions that are being pre-calculated.
|
||||
* <p>
|
||||
* The default value for this setting is 1000.
|
||||
* </p>
|
||||
*/
|
||||
public void setMaximumExpansionSize(int theMaximumExpansionSize) {
|
||||
Validate.isTrue(theMaximumExpansionSize > 0, "theMaximumExpansionSize must be > 0");
|
||||
|
|
|
@ -20,115 +20,8 @@ package ca.uhn.fhir.jpa.dao;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.hl7.fhir.instance.hapi.validation.FhirQuestionnaireResponseValidator;
|
||||
import org.hl7.fhir.instance.hapi.validation.HapiWorkerContext;
|
||||
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
|
||||
import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse;
|
||||
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.validation.FhirValidator;
|
||||
import ca.uhn.fhir.validation.IResourceLoader;
|
||||
import ca.uhn.fhir.validation.ValidationResult;
|
||||
|
||||
public class FhirResourceDaoQuestionnaireResponseDstu2 extends FhirResourceDaoDstu2<QuestionnaireResponse> {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("myFhirContextDstu2Hl7Org")
|
||||
private FhirContext myRefImplCtx;
|
||||
|
||||
private Boolean myValidateResponses;
|
||||
|
||||
@Autowired()
|
||||
@Qualifier("myJpaValidationSupportDstu2")
|
||||
private IValidationSupport myValidationSupport;
|
||||
|
||||
/**
|
||||
* Initialize the bean
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
try {
|
||||
Class.forName("org.hl7.fhir.dstu2.model.QuestionnaireResponse");
|
||||
myValidateResponses = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
myValidateResponses = Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateResourceForStorage(QuestionnaireResponse theResource, ResourceTable theEntityToSave) {
|
||||
super.validateResourceForStorage(theResource, theEntityToSave);
|
||||
if (!myValidateResponses) {
|
||||
return;
|
||||
}
|
||||
|
||||
QuestionnaireResponse qa = theResource;
|
||||
if (qa == null || qa.getQuestionnaire() == null || qa.getQuestionnaire().getReference() == null || qa.getQuestionnaire().getReference().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FhirValidator val = myRefImplCtx.newValidator();
|
||||
val.setValidateAgainstStandardSchema(false);
|
||||
val.setValidateAgainstStandardSchematron(false);
|
||||
|
||||
FhirQuestionnaireResponseValidator module = new FhirQuestionnaireResponseValidator();
|
||||
module.setResourceLoader(new JpaResourceLoader());
|
||||
module.setWorkerContext(new HapiWorkerContext(myRefImplCtx, myValidationSupport));
|
||||
val.registerValidatorModule(module);
|
||||
|
||||
ValidationResult result = val.validateWithResult(myRefImplCtx.newJsonParser().parseResource(getContext().newJsonParser().encodeResourceToString(qa)));
|
||||
if (!result.isSuccessful()) {
|
||||
IBaseOperationOutcome oo = getContext().newJsonParser().parseResource(OperationOutcome.class, myRefImplCtx.newJsonParser().encodeResourceToString(result.toOperationOutcome()));
|
||||
throw new UnprocessableEntityException(getContext(), oo);
|
||||
}
|
||||
}
|
||||
|
||||
public class JpaResourceLoader implements IResourceLoader {
|
||||
|
||||
public JpaResourceLoader() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IBaseResource> T load(Class<T> theType, IIdType theId) throws ResourceNotFoundException {
|
||||
|
||||
/*
|
||||
* The QuestionnaireResponse validator uses RI structures, so for now we need to convert between that and HAPI
|
||||
* structures. This is a bit hackish, but hopefully it will go away at some point.
|
||||
*/
|
||||
if ("ValueSet".equals(theType.getSimpleName())) {
|
||||
IFhirResourceDao<ValueSet> dao = getDao(ValueSet.class);
|
||||
ValueSet in = dao.read(theId, null);
|
||||
String encoded = getContext().newJsonParser().encodeResourceToString(in);
|
||||
|
||||
// TODO: this is temporary until structures-dstu2 catches up to structures-hl7org.dstu2
|
||||
encoded = encoded.replace("\"define\"", "\"codeSystem\"");
|
||||
|
||||
return myRefImplCtx.newJsonParser().parseResource(theType, encoded);
|
||||
} else if ("Questionnaire".equals(theType.getSimpleName())) {
|
||||
IFhirResourceDao<Questionnaire> dao = getDao(Questionnaire.class);
|
||||
Questionnaire vs = dao.read(theId, null);
|
||||
return myRefImplCtx.newJsonParser().parseResource(theType, getContext().newJsonParser().encodeResourceToString(vs));
|
||||
} else {
|
||||
// Should not happen, validator will only ask for these two
|
||||
throw new IllegalStateException("Unexpected request to load resource of type " + theType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// nothing
|
||||
}
|
||||
|
|
|
@ -39,7 +39,8 @@ import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
|||
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.util.SourceParam;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||
import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
|
||||
import ca.uhn.fhir.jpa.util.*;
|
||||
import ca.uhn.fhir.model.api.*;
|
||||
|
@ -64,7 +65,6 @@ import ca.uhn.fhir.util.UrlUtil;
|
|||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
|
@ -135,7 +135,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
@Autowired
|
||||
private ISearchParamRegistry mySearchParamRegistry;
|
||||
@Autowired
|
||||
private IHapiTerminologySvc myTerminologySvc;
|
||||
private ITermReadSvc myTerminologySvc;
|
||||
@Autowired
|
||||
private MatchUrlService myMatchUrlService;
|
||||
private List<Long> myAlsoIncludePids;
|
||||
|
@ -834,7 +834,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
} else if (allOrPids != null) {
|
||||
|
||||
SearchFilterParser.CompareOperation operation = defaultIfNull(theOperation, SearchFilterParser.CompareOperation.eq);
|
||||
assert operation == null || operation == SearchFilterParser.CompareOperation.eq || operation == SearchFilterParser.CompareOperation.ne;
|
||||
assert operation == SearchFilterParser.CompareOperation.eq || operation == SearchFilterParser.CompareOperation.ne;
|
||||
switch (operation) {
|
||||
default:
|
||||
case eq:
|
||||
|
@ -862,19 +862,9 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
List<Predicate> codePredicates = new ArrayList<>();
|
||||
|
||||
for (IQueryParameterType nextParameter : theList) {
|
||||
String nextParamValue = nextParameter.getValueAsQueryToken(myContext);
|
||||
int lastHashValueIndex = nextParamValue.lastIndexOf('#');
|
||||
String sourceUri;
|
||||
String requestId;
|
||||
if (lastHashValueIndex == -1) {
|
||||
sourceUri = nextParamValue;
|
||||
requestId = null;
|
||||
} else {
|
||||
sourceUri = nextParamValue.substring(0, lastHashValueIndex);
|
||||
requestId = nextParamValue.substring(lastHashValueIndex + 1);
|
||||
}
|
||||
requestId = left(requestId, Constants.REQUEST_ID_LENGTH);
|
||||
|
||||
SourceParam sourceParameter = new SourceParam(nextParameter.getValueAsQueryToken(myContext));
|
||||
String sourceUri = sourceParameter.getSourceUri();
|
||||
String requestId = sourceParameter.getRequestId();
|
||||
Predicate sourceUriPredicate = myBuilder.equal(join.get("mySourceUri"), sourceUri);
|
||||
Predicate requestIdPredicate = myBuilder.equal(join.get("myRequestId"), requestId);
|
||||
if (isNotBlank(sourceUri) && isNotBlank(requestId)) {
|
||||
|
|
|
@ -30,14 +30,15 @@ import ca.uhn.fhir.jpa.delete.DeleteConflictList;
|
|||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.util.DeleteConflict;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.api.*;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
|
@ -49,6 +50,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
|||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.util.ServletRequestUtil;
|
||||
import ca.uhn.fhir.util.*;
|
||||
import com.google.common.base.Charsets;
|
||||
|
@ -89,8 +91,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
|||
@Autowired
|
||||
private ITransactionProcessorVersionAdapter<BUNDLE, BUNDLEENTRY> myVersionAdapter;
|
||||
@Autowired
|
||||
private MatchUrlService myMatchUrlService;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired(required = false)
|
||||
private HapiFhirHibernateJpaDialect myHapiFhirHibernateJpaDialect;
|
||||
|
@ -127,7 +127,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
|||
}
|
||||
|
||||
ourLog.info("Beginning storing collection with {} resources", myVersionAdapter.getEntries(theRequest).size());
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
|
@ -173,7 +172,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
|||
}
|
||||
}
|
||||
idToPersistedOutcome.put(newId, outcome);
|
||||
if (outcome.getCreated().booleanValue()) {
|
||||
if (outcome.getCreated()) {
|
||||
myVersionAdapter.setResponseStatus(newEntry, toStatusString(Constants.STATUS_HTTP_201_CREATED));
|
||||
} else {
|
||||
myVersionAdapter.setResponseStatus(newEntry, toStatusString(Constants.STATUS_HTTP_200_OK));
|
||||
|
@ -517,7 +516,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
|||
* Look for duplicate conditional creates and consolidate them
|
||||
*/
|
||||
final HashMap<String, String> keyToUuid = new HashMap<>();
|
||||
final IdentityHashMap<IBaseResource, String> identityToUuid = new IdentityHashMap<>();
|
||||
for (int index = 0, originalIndex = 0; index < theEntries.size(); index++, originalIndex++) {
|
||||
BUNDLEENTRY nextReqEntry = theEntries.get(index);
|
||||
IBaseResource resource = myVersionAdapter.getResource(nextReqEntry);
|
||||
|
@ -551,7 +549,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
|||
if (consolidateEntry) {
|
||||
if (!keyToUuid.containsKey(key)) {
|
||||
keyToUuid.put(key, entryUrl);
|
||||
identityToUuid.put(resource, entryUrl);
|
||||
} else {
|
||||
ourLog.info("Discarding transaction bundle entry {} as it contained a duplicate conditional {}", originalIndex, verb);
|
||||
theEntries.remove(index);
|
||||
|
|
|
@ -40,4 +40,8 @@ public interface IBinaryStorageEntityDao extends JpaRepository<BinaryStorageEnti
|
|||
|
||||
@Query("SELECT e FROM BinaryStorageEntity e WHERE e.myBlobId = :blob_id AND e.myResourceId = :resource_id")
|
||||
Optional<BinaryStorageEntity> findByIdAndResourceId(@Param("blob_id") String theBlobId, @Param("resource_id") String theResourceId);
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM BinaryStorageEntity t WHERE t.myBlobId = :pid")
|
||||
void deleteByPid(@Param("pid") String theId);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package ca.uhn.fhir.jpa.dao.data;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.BulkExportCollectionEntity;
|
||||
import ca.uhn.fhir.jpa.entity.BulkExportJobEntity;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
|
@ -30,5 +27,13 @@ import java.util.Optional;
|
|||
*/
|
||||
|
||||
public interface IBulkExportCollectionDao extends JpaRepository<BulkExportCollectionEntity, Long> {
|
||||
// nothing currently
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM BulkExportCollectionEntity t")
|
||||
void deleteAllFiles();
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM BulkExportCollectionEntity t WHERE t.myId = :pid")
|
||||
void deleteByPid(@Param("pid") Long theId);
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ package ca.uhn.fhir.jpa.dao.data;
|
|||
|
||||
import ca.uhn.fhir.jpa.entity.BulkExportCollectionFileEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -24,5 +27,13 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||
*/
|
||||
|
||||
public interface IBulkExportCollectionFileDao extends JpaRepository<BulkExportCollectionFileEntity, Long> {
|
||||
// nothing currently
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM BulkExportCollectionFileEntity t")
|
||||
void deleteAllFiles();
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM BulkExportCollectionFileEntity t WHERE t.myId = :pid")
|
||||
void deleteByPid(@Param("pid") Long theId);
|
||||
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue