Merge branch 'master' into ja_20190928_rationalize_search_param_extractor

This commit is contained in:
James Agnew 2019-10-27 17:25:20 -04:00
commit 817f9ae36e
2591 changed files with 89884 additions and 1505720 deletions

View File

@ -28,7 +28,12 @@ jobs:
mavenOptions: '-Xmx2048m $(MAVEN_OPTS) -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss,SSS -Duser.timezone=America/Toronto'
- script: bash <(curl https://codecov.io/bash) -t $(CODECOV_TOKEN)
displayName: 'codecov'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: 'JaCoCo'
summaryFileLocation: $(System.DefaultWorkingDirectory)/hapi-fhir-jacoco/target/site/jacoco-report/jacoco.xml
reportDirectory: $(System.DefaultWorkingDirectory)/hapi-fhir-jacoco/target/site/jacoco-report/
failIfCoverageEmpty: false
# Potential Additional Maven3 Options:
#publishJUnitResults: true
@ -53,3 +58,5 @@ jobs:
#checkStyleRunAnalysis: false # Optional
#pmdRunAnalysis: false # Optional
#findBugsRunAnalysis: false # Optional

View File

@ -1,5 +1,6 @@
package example;
import org.hl7.fhir.converter.NullVersionConverterAdvisor30;
import org.hl7.fhir.convertors.*;
import org.hl7.fhir.exceptions.FHIRException;

View File

@ -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,33 +19,20 @@ public class ServerMetadataExamples {
Patient patient = new Patient();
retVal.add(patient);
patient.setId("Patient/123");
patient.addName().addFamily("Smith").addGiven("John");
patient.addName().setFamily("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);
// 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 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);
// Set the last updated date
patient.getMeta().setLastUpdatedElement(new InstantType("2011-02-22T11:22:00.0122Z"));
return retVal;
}

View File

@ -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();

View File

@ -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;
}

View File

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

View File

@ -20,25 +20,25 @@ package ca.uhn.fhir.context;
* #L%
*/
import java.lang.reflect.Constructor;
import java.util.*;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBase;
import java.lang.reflect.Constructor;
import java.util.*;
public abstract class BaseRuntimeElementDefinition<T extends IBase> {
private static final Class<Void> VOID_CLASS = Void.class;
private final Class<? extends T> myImplementingClass;
private final String myName;
private final boolean myStandardType;
private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<T>>());
private List<RuntimeChildDeclaredExtensionDefinition> myExtensions = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsNonModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
private final Class<? extends T> myImplementingClass;
private final String myName;
private final boolean myStandardType;
private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
private BaseRuntimeElementDefinition<?> myRootParentDefinition;
public BaseRuntimeElementDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
assert StringUtils.isNotBlank(theName);
@ -168,9 +168,14 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
}
}
public BaseRuntimeElementDefinition<?> getRootParentDefinition() {
return myRootParentDefinition;
}
/**
* Invoked prior to use to perform any initialization and make object
* mutable.
*
* @param theContext TODO
*/
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
@ -193,6 +198,16 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
}
myExtensions = Collections.unmodifiableList(myExtensions);
Class parent = myImplementingClass;
do {
BaseRuntimeElementDefinition<?> parentDefinition = theClassToElementDefinitions.get(parent);
if (parentDefinition != null) {
myRootParentDefinition = parentDefinition;
}
parent = parent.getSuperclass();
} while (!parent.equals(Object.class));
}
@Override
@ -210,15 +225,18 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
}
public enum ChildTypeEnum {
COMPOSITE_DATATYPE, /**
COMPOSITE_DATATYPE,
/**
* HL7.org structure style.
*/
CONTAINED_RESOURCE_LIST, /**
CONTAINED_RESOURCE_LIST,
/**
* HAPI structure style.
*/
CONTAINED_RESOURCES, EXTENSION_DECLARED,
ID_DATATYPE,
PRIMITIVE_DATATYPE, /**
PRIMITIVE_DATATYPE,
/**
* HAPI style.
*/
PRIMITIVE_XHTML,

View File

@ -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;
@ -898,6 +899,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}
*/

View File

@ -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();

View File

@ -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");
}
}
}
}

View File

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

View File

@ -1,64 +0,0 @@
package ca.uhn.fhir.model.api;
/*
* #%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.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.util.ElementUtil;
public class BaseBundle extends BaseElement /*implements IElement*/ {
private static final long serialVersionUID = 3349586533271409727L;
private StringDt myAuthorName;
private StringDt myAuthorUri;
private IdDt myId;
public StringDt getAuthorName() {
if (myAuthorName == null) {
myAuthorName = new StringDt();
}
return myAuthorName;
}
public StringDt getAuthorUri() {
if (myAuthorUri == null) {
myAuthorUri = new StringDt();
}
return myAuthorUri;
}
public IdDt getId() {
if (myId==null) {
myId=new IdDt();
}
return myId;
}
@Override
public boolean isEmpty() {
return ElementUtil.isEmpty(myAuthorName, myAuthorUri);
}
public void setId(IdDt theId) {
myId = theId;
}
}

View File

@ -40,7 +40,7 @@ public interface IQueryParameterAnd<T extends IQueryParameterOr<?>> extends Seri
* @param theContext TODO
* @param theParamName TODO
*/
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters) throws InvalidRequestException;
void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters) throws InvalidRequestException;
/**
*
@ -50,7 +50,7 @@ public interface IQueryParameterAnd<T extends IQueryParameterOr<?>> extends Seri
* for information on the <b>token</b> format
* </p>
*/
public List<T> getValuesAsQueryTokens();
List<T> getValuesAsQueryTokens();
}

View File

@ -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);
}
private static DecimalDt getDecimalFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<DecimalDt> theKey) {
Object retValObj = theResourceMetadata.get(theKey);
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 theUrl) {
super(theUrl);
}
@Override
public ExtensionDt get(IResource theResource) {
Object retValObj = theResource.getResourceMetadata().get(this);
if (retValObj == null) {
return null;
} else if (retValObj instanceof DecimalDt) {
if (((DecimalDt) retValObj).isEmpty()) {
return null;
} else if (retValObj instanceof ExtensionDt) {
return (ExtensionDt) retValObj;
}
return (DecimalDt) retValObj;
} else if (retValObj instanceof String) {
if (StringUtils.isBlank((String) retValObj)) {
return null;
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName()
+ "' in resource metadata for key " + this.name() + " - Expected "
+ ExtensionDt.class.getCanonicalName());
}
return new DecimalDt((String) retValObj);
} else if (retValObj instanceof Double) {
return new DecimalDt((Double) retValObj);
@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);
}
}
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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;
}

View File

@ -26,7 +26,9 @@ import java.util.*;
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";
@ -241,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;

View File

@ -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;
@ -108,6 +108,13 @@ 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;
}

View File

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

View File

@ -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) {

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -1,19 +1,20 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
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;
import java.util.Objects;
import java.util.function.Consumer;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -42,37 +43,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/
public class BundleUtil {
public static class BundleEntryParts {
private final RequestTypeEnum myRequestType;
private final IBaseResource myResource;
private final String myUrl;
private final String myConditionalUrl;
BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource, String theConditionalUrl) {
super();
myRequestType = theRequestType;
myUrl = theUrl;
myResource = theResource;
myConditionalUrl = theConditionalUrl;
}
public RequestTypeEnum getRequestType() {
return myRequestType;
}
public IBaseResource getResource() {
return myResource;
}
public String getConditionalUrl() {
return myConditionalUrl;
}
public String getUrl() {
return myUrl;
}
}
/**
* @return Returns <code>null</code> if the link isn't found or has no value
*/
@ -185,20 +155,25 @@ public class BundleUtil {
* Extract all of the resources from a given bundle
*/
public static List<BundleEntryParts> toListOfEntries(FhirContext theContext, IBaseBundle theBundle) {
List<BundleEntryParts> retVal = new ArrayList<>();
EntryListAccumulator entryListAccumulator = new EntryListAccumulator();
processEntries(theContext, theBundle, entryListAccumulator);
return entryListAccumulator.getList();
}
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
public static void processEntries(FhirContext theContext, IBaseBundle theBundle, Consumer<ModifiableBundleEntry> theProcessor) {
RuntimeResourceDefinition bundleDef = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChildDef = bundleDef.getChildByName("entry");
List<IBase> entries = entryChildDef.getAccessor().getValues(theBundle);
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestElem = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
BaseRuntimeChildDefinition requestUrlChild = requestElem.getChildByName("url");
BaseRuntimeChildDefinition requestIfNoneExistChild = requestElem.getChildByName("ifNoneExist");
BaseRuntimeChildDefinition methodChild = requestElem.getChildByName("method");
BaseRuntimeElementCompositeDefinition<?> entryChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource");
BaseRuntimeChildDefinition requestChildDef = entryChildContentsDef.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) requestChildDef.getChildByName("request");
BaseRuntimeChildDefinition requestUrlChildDef = requestChildContentsDef.getChildByName("url");
BaseRuntimeChildDefinition requestIfNoneExistChildDef = requestChildContentsDef.getChildByName("ifNoneExist");
BaseRuntimeChildDefinition methodChildDef = requestChildContentsDef.getChildByName("method");
for (IBase nextEntry : entries) {
IBaseResource resource = null;
@ -206,15 +181,15 @@ public class BundleUtil {
RequestTypeEnum requestType = null;
String conditionalUrl = null;
for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
resource = (IBaseResource) next;
for (IBase nextResource : resourceChildDef.getAccessor().getValues(nextEntry)) {
resource = (IBaseResource) nextResource;
}
for (IBase nextRequest : requestChild.getAccessor().getValues(nextEntry)) {
for (IBase nextUrl : requestUrlChild.getAccessor().getValues(nextRequest)) {
for (IBase nextRequest : requestChildDef.getAccessor().getValues(nextEntry)) {
for (IBase nextUrl : requestUrlChildDef.getAccessor().getValues(nextRequest)) {
url = ((IPrimitiveType<?>) nextUrl).getValueAsString();
}
for (IBase nextUrl : methodChild.getAccessor().getValues(nextRequest)) {
String methodString = ((IPrimitiveType<?>) nextUrl).getValueAsString();
for (IBase nextMethod : methodChildDef.getAccessor().getValues(nextRequest)) {
String methodString = ((IPrimitiveType<?>) nextMethod).getValueAsString();
if (isNotBlank(methodString)) {
requestType = RequestTypeEnum.valueOf(methodString);
}
@ -227,7 +202,7 @@ public class BundleUtil {
conditionalUrl = url != null && url.contains("?") ? url : null;
break;
case POST:
List<IBase> ifNoneExistReps = requestIfNoneExistChild.getAccessor().getValues(nextRequest);
List<IBase> ifNoneExistReps = requestIfNoneExistChildDef.getAccessor().getValues(nextRequest);
if (ifNoneExistReps.size() > 0) {
IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) ifNoneExistReps.get(0);
conditionalUrl = ifNoneExist.getValueAsString();
@ -241,11 +216,10 @@ public class BundleUtil {
* All 3 might be null - That's ok because we still want to know the
* order in the original bundle.
*/
retVal.add(new BundleEntryParts(requestType, url, resource, conditionalUrl));
BundleEntryMutator mutator = new BundleEntryMutator(nextEntry, requestChildDef, requestChildContentsDef);
ModifiableBundleEntry entry = new ModifiableBundleEntry(new BundleEntryParts(requestType, url, resource, conditionalUrl), mutator);
theProcessor.accept(entry);
}
return retVal;
}
/**
@ -278,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;
}
}

View File

@ -735,25 +735,15 @@ public class FhirTerser {
valueType = (Class<? extends IBase>) valueType.getSuperclass();
}
if (childElementDef == null) {
StringBuilder b = new StringBuilder();
b.append("Found value of type[");
b.append(nextValue.getClass().getSimpleName());
b.append("] which is not valid for field[");
b.append(nextChild.getElementName());
b.append("] in ");
b.append(childDef.getName());
b.append(" - Valid types: ");
for (Iterator<String> iter = new TreeSet<>(nextChild.getValidChildNames()).iterator(); iter.hasNext(); ) {
BaseRuntimeElementDefinition<?> childByName = nextChild.getChildByName(iter.next());
b.append(childByName.getImplementingClass().getSimpleName());
if (iter.hasNext()) {
b.append(", ");
}
}
throw new DataFormatException(b.toString());
Class<? extends IBase> typeClass = nextValue.getClass();
while (childElementDef == null && IBase.class.isAssignableFrom(typeClass)) {
//noinspection unchecked
typeClass = (Class<? extends IBase>) typeClass.getSuperclass();
childElementDef = nextChild.getChildElementDefinitionByDatatype(typeClass);
}
Validate.notNull(childElementDef, "Found value of type[%s] which is not valid for field[%s] in %s", nextValue.getClass(), nextChild.getElementName(), childDef.getName());
visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
}
}

View File

@ -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];
}
}

View File

@ -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; }
}

View File

@ -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");

View File

@ -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);

View File

@ -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")

View File

@ -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")

View File

@ -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,13 +266,12 @@ public class StopWatch {
*/
public void startTask(String theTaskName) {
endCurrentTask();
if (isNotBlank(theTaskName)) {
Validate.notBlank(theTaskName, "Task name must not be blank");
myCurrentTask = new TaskTiming()
.setTaskName(theTaskName)
.setStart(now());
myTasks.add(myCurrentTask);
}
}
/**
* Formats value in an appropriate format. See {@link #formatMillis(long)}}
@ -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();

View File

@ -8,6 +8,8 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.google.common.escape.Escaper;
import com.google.common.net.PercentEscaper;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.io.UnsupportedEncodingException;
@ -457,4 +459,23 @@ public class UrlUtil {
return theString;
}
public static List<NameValuePair> translateMatchUrl(String theMatchUrl) {
List<NameValuePair> parameters;
String matchUrl = theMatchUrl;
int questionMarkIndex = matchUrl.indexOf('?');
if (questionMarkIndex != -1) {
matchUrl = matchUrl.substring(questionMarkIndex + 1);
}
matchUrl = matchUrl.replace("|", "%7C");
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
matchUrl = matchUrl.replace("=>", "=%3E");
matchUrl = matchUrl.replace("=<", "=%3C");
if (matchUrl.contains(" ")) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
}
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
return parameters;
}
}

View File

@ -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 {
@ -1507,6 +1513,73 @@ public class XmlUtil {
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
{
try {
isWoodstox = inputFactory.getProperty("org.codehaus.stax2.implVersion") != null;
}
catch ( Exception e )
{
} 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 {
@ -1767,68 +1834,30 @@ public class 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)));
}
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);
}
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;
InputSource src = new InputSource(new StringReader(theInput));
return builder.parse(src);
}
}
if (!hasEscapable) {
theW.write(theCbuf, theOff, theLen);
return;
}
String escaped = StringEscapeUtils.escapeXml10(new String(theCbuf, theOff, theLen));
theW.write(escaped.toCharArray());
}
};
}
}
}

View File

@ -0,0 +1,48 @@
package ca.uhn.fhir.util.bundle;
/*-
* #%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.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
public class BundleEntryMutator {
private final IBase myEntry;
private final BaseRuntimeChildDefinition myRequestChildDef;
private final BaseRuntimeElementCompositeDefinition<?> myRequestChildContentsDef;
public BundleEntryMutator(IBase theEntry, BaseRuntimeChildDefinition theRequestChildDef, BaseRuntimeElementCompositeDefinition<?> theRequestChildContentsDef) {
myEntry = theEntry;
myRequestChildDef = theRequestChildDef;
myRequestChildContentsDef = theRequestChildContentsDef;
}
void setRequestUrl(FhirContext theFhirContext, String theRequestUrl) {
BaseRuntimeChildDefinition requestUrlChildDef = myRequestChildContentsDef.getChildByName("url");
IPrimitiveType<?> url = ParametersUtil.createUri(theFhirContext, theRequestUrl);
for (IBase nextRequest : myRequestChildDef.getAccessor().getValues(myEntry)) {
requestUrlChildDef.getMutator().addValue(nextRequest, url);
}
}
}

View File

@ -0,0 +1,55 @@
package ca.uhn.fhir.util.bundle;
/*-
* #%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.api.RequestTypeEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
public class BundleEntryParts {
private final RequestTypeEnum myRequestType;
private final IBaseResource myResource;
private final String myUrl;
private final String myConditionalUrl;
public BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource, String theConditionalUrl) {
super();
myRequestType = theRequestType;
myUrl = theUrl;
myResource = theResource;
myConditionalUrl = theConditionalUrl;
}
public RequestTypeEnum getRequestType() {
return myRequestType;
}
public IBaseResource getResource() {
return myResource;
}
public String getConditionalUrl() {
return myConditionalUrl;
}
public String getUrl() {
return myUrl;
}
}

View File

@ -0,0 +1,38 @@
package ca.uhn.fhir.util.bundle;
/*-
* #%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.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class EntryListAccumulator implements Consumer<ModifiableBundleEntry> {
private final List<BundleEntryParts> myList = new ArrayList<>();
@Override
public void accept(ModifiableBundleEntry theModifiableBundleEntry) {
myList.add(theModifiableBundleEntry.getBundleEntryParts());
}
public List<BundleEntryParts> getList() {
return myList;
}
}

View File

@ -0,0 +1,45 @@
package ca.uhn.fhir.util.bundle;
/*-
* #%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.context.FhirContext;
public class ModifiableBundleEntry {
private final BundleEntryParts myBundleEntryParts;
private final BundleEntryMutator myBundleEntryMutator;
public ModifiableBundleEntry(BundleEntryParts theBundleEntryParts, BundleEntryMutator theBundleEntryMutator) {
myBundleEntryParts = theBundleEntryParts;
myBundleEntryMutator = theBundleEntryMutator;
}
BundleEntryParts getBundleEntryParts() {
return myBundleEntryParts;
}
public void setRequestUrl(FhirContext theFhirContext, String theRequestUrl) {
myBundleEntryMutator.setRequestUrl(theFhirContext, theRequestUrl);
}
public String getRequestUrl() {
return myBundleEntryParts.getUrl();
}
}

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.model.primitive;
package ca.uhn.fhir.validation;
/*
/*-
* #%L
* HAPI FHIR - Core Library
* %%
@ -20,19 +20,13 @@ package ca.uhn.fhir.model.primitive;
* #L%
*/
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
/**
* 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 {
@DatatypeDef(name = "idref")
public class IdrefDt extends StringDt {
// nothing extra yet
private IElement myTarget;
public IElement getTarget() {
return myTarget;
}
public void setTarget(IElement theTarget) {
myTarget = theTarget;
}
}

View File

@ -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();
@ -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,7 +120,7 @@ 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());
@ -129,69 +132,40 @@ public class SchemaBaseValidator implements IValidatorModule {
* 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});
} 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);
try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) {
if (baseIs == null) {
throw new InternalErrorException("Schema not found. " + RESOURCES_JAR_NOTE);
}
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;
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);
}
}
@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);
}
}
}

View File

@ -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()) {

View File

@ -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);

View File

@ -25,6 +25,8 @@ public interface IBaseBinary extends IBaseResource {
byte[] getContent();
IPrimitiveType<byte[]> getContentElement();
String getContentAsBase64();
String getContentType();

View File

@ -44,6 +44,6 @@ public interface IBaseEnumFactory<T extends Enum<?>> extends Serializable {
/**
* Get the system for a given enum value
*/
String toSystem(T theValue);
default String toSystem(T theValue) { return null; }
}

View File

@ -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}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,12 @@
package ca.uhn.fhir.rest.api;
import org.junit.Test;
public class ConstantsTest {
@Test
public void testConstants() {
new Constants();
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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(",", "."));
}
}

View File

@ -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());
}
}

View File

@ -248,6 +248,13 @@
<artifactId>jansi</artifactId>
</dependency>
<!-- Test Deps -->
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -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);

View File

@ -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;
}
}

View File

@ -123,16 +123,13 @@ 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
try (Writer writer = Files.newBufferedWriter(path)) {
CSVFormat format = CSVFormat.DEFAULT
.withRecordSeparator("\n")
.withHeader(Header.class)
.withQuoteMode(QuoteMode.ALL));
) {
.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()) {
@ -153,6 +150,8 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept
}
}
}
csvPrinter.flush();
}
} catch (IOException ioe) {
throw new InternalErrorException(ioe);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -20,30 +20,48 @@ package ca.uhn.fhir.cli;
* #L%
*/
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
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.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class UploadTerminologyCommand extends BaseCommand {
// 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 String UPLOAD_EXTERNAL_CODE_SYSTEM = "upload-external-code-system";
private static final long DEFAULT_TRANSFER_SIZE_LIMIT = 10 * FileUtils.ONE_MB;
private static long ourTransferSizeLimit = DEFAULT_TRANSFER_SIZE_LIMIT;
@Override
public String getCommandDescription() {
return "Uploads a terminology package (e.g. a SNOMED CT ZIP file) to a server, using the $" + UPLOAD_EXTERNAL_CODE_SYSTEM + " operation.";
return "Uploads a terminology package (e.g. a SNOMED CT ZIP file or a custom terminology bundle) to a server, using the appropriate operation.";
}
@Override
public String getCommandName() {
return "upload-terminology";
return UPLOAD_TERMINOLOGY;
}
@Override
@ -52,9 +70,9 @@ 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);
@ -65,6 +83,14 @@ public class UploadTerminologyCommand extends BaseCommand {
public void run(CommandLine theCommandLine) throws ParseException {
parseFhirContext(theCommandLine);
ModeEnum mode;
String modeString = theCommandLine.getOptionValue("m", "SNAPSHOT");
try {
mode = ModeEnum.valueOf(modeString);
} catch (IllegalArgumentException e) {
throw new ParseException("Invalid mode: " + modeString);
}
String termUrl = theCommandLine.getOptionValue("u");
if (isBlank(termUrl)) {
throw new ParseException("No URL provided");
@ -77,29 +103,166 @@ public class UploadTerminologyCommand extends BaseCommand {
IGenericClient client = super.newClient(theCommandLine);
IBaseParameters inputParameters = ParametersUtil.newInstance(myFhirCtx);
ParametersUtil.addParameterToParametersUri(myFhirCtx, inputParameters, "url", termUrl);
for (String next : datafile) {
ParametersUtil.addParameterToParametersString(myFhirCtx, inputParameters, "localfile", next);
}
if (theCommandLine.hasOption("custom")) {
ParametersUtil.addParameterToParametersCode(myFhirCtx, inputParameters, "contentMode", "custom");
}
if (theCommandLine.hasOption(VERBOSE_LOGGING_PARAM)) {
client.registerInterceptor(new LoggingInterceptor(true));
}
switch (mode) {
case SNAPSHOT:
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM);
break;
case ADD:
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD);
break;
case REMOVE:
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE);
break;
}
}
private void invokeOperation(String theTermUrl, String[] theDatafile, IGenericClient theClient, IBaseParameters theInputParameters, String theOperationName) throws ParseException {
ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_SYSTEM, theTermUrl);
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);
}
}
}
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...");
IBaseParameters response = client
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(UPLOAD_EXTERNAL_CODE_SYSTEM)
.withParameters(inputParameters)
.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(e);
}
}
ICompositeType attachment = AttachmentUtil.newInstance(myFhirCtx);
AttachmentUtil.setUrl(myFhirCtx, attachment, fileName);
if (bytes != null) {
AttachmentUtil.setData(myFhirCtx, attachment, bytes);
}
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;
}
}

View File

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

View File

@ -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);
@ -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

View File

@ -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);
@ -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

View File

@ -0,0 +1,367 @@
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.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;
import org.eclipse.jetty.servlet.ServletHandler;
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;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.*;
import java.util.List;
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 extends BaseTest {
static {
System.setProperty("test", "true");
}
private Server myServer;
private FhirContext myCtx = FhirContext.forR4();
@Mock
private ITermLoaderSvc myTermLoaderSvc;
@Captor
private ArgumentCaptor<List<ITermLoaderSvc.FileDescriptor>> myDescriptorListCaptor;
private int myPort;
private String myConceptsFileName = "target/concepts.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 testDeltaAdd() throws IOException {
writeConceptAndHierarchyFiles();
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", myConceptsFileName,
"-d", myHierarchyFileName
});
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(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));
}
@Test
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();
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,
"-v", "r4",
"-m", "REMOVE",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myConceptsFileName,
"-d", myHierarchyFileName
});
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));
}
@Test
public void testSnapshot() throws IOException {
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());
assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename());
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
}
/**
* 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());
}
}
private void writeConceptAndHierarchyFiles() throws IOException {
try (FileWriter w = new FileWriter(myConceptsFile, false)) {
w.append("CODE,DISPLAY\n");
w.append("ANIMALS,Animals\n");
w.append("CATS,Cats\n");
w.append("DOGS,Dogs\n");
}
try (FileWriter w = new FileWriter(myHierarchyFile, false)) {
w.append("PARENT,CHILD\n");
w.append("ANIMALS,CATS\n");
w.append("ANIMALS,DOGS\n");
}
}
@After
public void after() throws Exception {
JettyUtil.closeServer(myServer);
FileUtils.deleteQuietly(myConceptsFile);
FileUtils.deleteQuietly(myHierarchyFile);
FileUtils.deleteQuietly(myArchiveFile);
FileUtils.deleteQuietly(myCodeSystemFile);
FileUtils.deleteQuietly(myTextFile);
UploadTerminologyCommand.setTransferSizeLimitForUnitTest(-1);
}
@Before
public void before() throws Exception {
myServer = new Server(0);
TerminologyUploaderProvider provider = new TerminologyUploaderProvider(myCtx, myTermLoaderSvc);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(myCtx);
servlet.registerProvider(provider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
myServer.setHandler(proxyHandler);
JettyUtil.startServer(myServer);
myPort = JettyUtil.getPortForStartedServer(myServer);
}
}

View File

@ -51,17 +51,12 @@ 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();
try (InputStream responseEntity = readEntity()) {
if (responseEntity != null) {
myEntityBuffered = true;
try {
@ -71,6 +66,7 @@ public class OkHttpRestfulResponse extends BaseHttpResponse implements IHttpResp
}
}
}
}
@Override
public void close() {

View File

@ -87,11 +87,13 @@ public class ApacheHttpClient extends BaseHttpClient implements IHttpClient {
}
@Override
protected IHttpRequest createHttpRequest() {
IHttpRequest retVal = createHttpRequest((HttpEntity)null);
return retVal;
}
@Override
protected IHttpRequest createHttpRequest(byte[] content) {
/*
* Note: Be careful about changing which constructor we use for
@ -109,6 +111,7 @@ public class ApacheHttpClient extends BaseHttpClient implements IHttpClient {
return result;
}
@Override
protected IHttpRequest createHttpRequest(Map<String, List<String>> theParams) {
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
for (Entry<String, List<String>> nextParam : theParams.entrySet()) {
@ -124,6 +127,7 @@ public class ApacheHttpClient extends BaseHttpClient implements IHttpClient {
}
@Override
protected IHttpRequest createHttpRequest(String theContents) {
/*
* We aren't using a StringEntity here because the constructors

View File

@ -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,28 +54,22 @@ 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();
try (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);
}
}
}
}
@Override
public void close() {
@ -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());
}

View File

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

View File

@ -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);
}

View File

@ -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;
@ -76,6 +77,7 @@ public class LoggingInterceptor implements IClientInterceptor {
}
}
@Override
@Hook(Pointcut.CLIENT_REQUEST)
public void interceptRequest(IHttpRequest theRequest) {
if (myLogRequestSummary) {
@ -93,14 +95,13 @@ 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);
}
}
}
@Override
@Hook(Pointcut.CLIENT_RESPONSE)
public void interceptResponse(IHttpResponse theResponse) throws IOException {
if (myLogResponseSummary) {
@ -145,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 {
@ -157,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);
}
}
}
@ -176,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');
}

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.rest.client.method;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

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

View File

@ -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;

View File

@ -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;
}

View File

@ -35,10 +35,12 @@ public class IgPackParserDstu2 extends BaseIgPackParser<IValidationSupport> {
super(massage(theCtx));
}
@Override
protected IValidationSupport createValidationSupport(Map<IIdType, IBaseResource> theIgResources) {
return new IgPackValidationSupportDstu2(theIgResources);
}
@Override
protected FhirVersionEnum provideExpectedVersion() {
return FhirVersionEnum.DSTU2_HL7ORG;
}

View File

@ -37,10 +37,12 @@ public class IgPackParserDstu3 extends BaseIgPackParser<IValidationSupport> {
super(theCtx);
}
@Override
protected IValidationSupport createValidationSupport(Map<IIdType, IBaseResource> theIgResources) {
return new IgPackValidationSupportDstu3(theIgResources);
}
@Override
protected FhirVersionEnum provideExpectedVersion() {
return FhirVersionEnum.DSTU3;
}

View File

@ -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;
}

View File

@ -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()) {

View File

@ -120,5 +120,4 @@
</plugin>
</plugins>
</build>
</project>

View File

@ -46,7 +46,6 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.3</version>
</dependency>
<dependency>

View File

@ -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;
}
}

View File

@ -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();
}
@Nonnull
private IBinaryTarget findAttachmentForRequest(IBaseResource theResource, String thePath, ServletRequestDetails theRequestDetails) {
FhirContext ctx = theRequestDetails.getFhirContext();
public void replaceDataWithExtension(IBinaryTarget theTarget, String theBlobId) {
theTarget
.getTarget()
.getExtension()
.removeIf(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()));
theTarget.setData(null);
Optional<IBase> type = ctx.newFluentPath().evaluateFirst(theResource, thePath, IBase.class);
String resType = myCtx.getResourceDefinition(theResource).getName();
if (!type.isPresent()) {
String msg = myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownPath", resType, thePath);
throw new InvalidRequestException(msg);
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) {
Optional<IBase> type = myCtx.newFluentPath().evaluateFirst(theResource, thePath, IBase.class);
String resType = this.myCtx.getResourceDefinition(theResource).getName();
if (!type.isPresent()) {
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();
}
}

View File

@ -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;
}
}
}

View File

@ -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());
@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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 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
* Fetch the contents of the given blob
*
* @param theResourceId The resource ID
* @param theBlobId The blob ID
* @return The payload as a byte array
*/
@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;
}
}
byte[] fetchBlob(IIdType theResourceId, String theBlobId) throws IOException;
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

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

View File

@ -6,8 +6,8 @@ import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider;
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
@ -28,10 +28,17 @@ import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
import ca.uhn.fhir.jpa.subscription.dbmatcher.CompositeInMemoryDaoSubscriptionMatcher;
import ca.uhn.fhir.jpa.subscription.dbmatcher.DaoSubscriptionMatcher;
import ca.uhn.fhir.jpa.subscription.module.cache.ISubscribableChannelFactory;
import ca.uhn.fhir.jpa.subscription.module.cache.LinkedBlockingQueueSubscribableChannelFactory;
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");

View File

@ -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();
}

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