Merge branch 'master' into ja_20190928_rationalize_search_param_extractor
This commit is contained in:
commit
cd84af30b1
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package example;
|
||||
|
||||
import org.hl7.fhir.converter.NullVersionConverterAdvisor30;
|
||||
import org.hl7.fhir.convertors.*;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
package example;
|
||||
|
||||
import ca.uhn.fhir.model.api.Tag;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import org.hl7.fhir.r4.model.InstantType;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.api.Tag;
|
||||
import ca.uhn.fhir.model.api.TagList;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
|
||||
public class ServerMetadataExamples {
|
||||
|
||||
// START SNIPPET: serverMethod
|
||||
|
@ -21,34 +19,21 @@ public class ServerMetadataExamples {
|
|||
Patient patient = new Patient();
|
||||
retVal.add(patient);
|
||||
patient.setId("Patient/123");
|
||||
patient.addName().addFamily("Smith").addGiven("John");
|
||||
|
||||
// Create a tag list and add it to the resource
|
||||
TagList tags = new TagList();
|
||||
tags.addTag(Tag.HL7_ORG_FHIR_TAG, "http://foo/tag1.html", "Some tag");
|
||||
tags.addTag(Tag.HL7_ORG_FHIR_TAG, "http://foo/tag2.html", "Another tag");
|
||||
ResourceMetadataKeyEnum.TAG_LIST.put(patient, tags);
|
||||
|
||||
// Set some links (these can be provided as relative links or absolute)
|
||||
// and the server will convert to absolute as appropriate
|
||||
String linkAlternate = "Patient/7736";
|
||||
ResourceMetadataKeyEnum.LINK_ALTERNATE.put(patient, linkAlternate);
|
||||
String linkSearch = "Patient?name=smith&name=john";
|
||||
ResourceMetadataKeyEnum.LINK_SEARCH.put(patient, linkSearch);
|
||||
|
||||
// Set the published and updated dates
|
||||
InstantDt pubDate = new InstantDt("2011-02-22");
|
||||
ResourceMetadataKeyEnum.PUBLISHED.put(patient, pubDate);
|
||||
InstantDt updatedDate = new InstantDt("2014-07-12T11:22:27Z");
|
||||
ResourceMetadataKeyEnum.UPDATED.put(patient, updatedDate);
|
||||
|
||||
// Set the resource title (note that if you are using HAPI's narrative
|
||||
// generation capability, the narrative generator will often create
|
||||
// useful titles automatically, and the server will create a default
|
||||
// title if none is provided)
|
||||
String title = "Patient John SMITH";
|
||||
ResourceMetadataKeyEnum.TITLE.put(patient, title);
|
||||
patient.addName().setFamily("Smith").addGiven("John");
|
||||
|
||||
// Add tags
|
||||
patient.getMeta().addTag()
|
||||
.setSystem(Tag.HL7_ORG_FHIR_TAG)
|
||||
.setCode("some_tag")
|
||||
.setDisplay("Some tag");
|
||||
patient.getMeta().addTag()
|
||||
.setSystem(Tag.HL7_ORG_FHIR_TAG)
|
||||
.setCode("another_tag")
|
||||
.setDisplay("Another tag");
|
||||
|
||||
// Set the last updated date
|
||||
patient.getMeta().setLastUpdatedElement(new InstantType("2011-02-22T11:22:00.0122Z"));
|
||||
|
||||
return retVal;
|
||||
}
|
||||
// END SNIPPET: serverMethod
|
||||
|
|
|
@ -36,9 +36,12 @@ public class ServerOperations {
|
|||
public void manualInputAndOutput(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws IOException {
|
||||
String contentType = theServletRequest.getContentType();
|
||||
byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream());
|
||||
|
||||
ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length);
|
||||
|
||||
// In a real example we might do something more interesting with the received bytes,
|
||||
// here we'll just replace them with hardcoded ones
|
||||
bytes = new byte[] { 0, 1, 2, 3 };
|
||||
|
||||
theServletResponse.setContentType(contentType);
|
||||
theServletResponse.getOutputStream().write(bytes);
|
||||
theServletResponse.getOutputStream().close();
|
||||
|
|
|
@ -265,7 +265,7 @@ public class ValidatorExamples {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) {
|
||||
// TODO: implement
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -100,10 +100,6 @@
|
|||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
|
@ -120,6 +116,11 @@
|
|||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import ca.uhn.fhir.util.ReflectionUtil;
|
|||
import ca.uhn.fhir.util.VersionUtil;
|
||||
import ca.uhn.fhir.validation.FhirValidator;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.jena.riot.Lang;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
|
@ -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}
|
||||
*/
|
||||
|
|
|
@ -29,6 +29,7 @@ import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
|||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
|
@ -44,56 +45,47 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
class ModelScanner {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
|
||||
|
||||
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
|
||||
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<>();
|
||||
private FhirContext myContext;
|
||||
private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
|
||||
private Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinitions = new HashMap<String, BaseRuntimeElementDefinition<?>>();
|
||||
private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<String, RuntimeResourceDefinition>();
|
||||
private Map<String, Class<? extends IBaseResource>> myNameToResourceType = new HashMap<String, Class<? extends IBaseResource>>();
|
||||
private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = new HashMap<>();
|
||||
private Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinitions = new HashMap<>();
|
||||
private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<>();
|
||||
private Map<String, Class<? extends IBaseResource>> myNameToResourceType = new HashMap<>();
|
||||
private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
|
||||
private Set<Class<? extends IBase>> myScanAlso = new HashSet<Class<? extends IBase>>();
|
||||
private Set<Class<? extends IBase>> myScanAlso = new HashSet<>();
|
||||
private FhirVersionEnum myVersion;
|
||||
|
||||
private Set<Class<? extends IBase>> myVersionTypes;
|
||||
|
||||
ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions,
|
||||
Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
|
||||
@Nonnull Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
|
||||
myContext = theContext;
|
||||
myVersion = theVersion;
|
||||
Set<Class<? extends IBase>> toScan;
|
||||
if (theResourceTypes != null) {
|
||||
toScan = new HashSet<Class<? extends IBase>>(theResourceTypes);
|
||||
} else {
|
||||
toScan = new HashSet<Class<? extends IBase>>();
|
||||
}
|
||||
Set<Class<? extends IBase>> toScan = new HashSet<>(theResourceTypes);
|
||||
init(theExistingDefinitions, toScan);
|
||||
}
|
||||
|
||||
public Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
|
||||
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
|
||||
return myClassToElementDefinitions;
|
||||
}
|
||||
|
||||
public Map<String, RuntimeResourceDefinition> getIdToResourceDefinition() {
|
||||
Map<String, RuntimeResourceDefinition> getIdToResourceDefinition() {
|
||||
return myIdToResourceDefinition;
|
||||
}
|
||||
|
||||
public Map<String, BaseRuntimeElementDefinition<?>> getNameToElementDefinitions() {
|
||||
Map<String, BaseRuntimeElementDefinition<?>> getNameToElementDefinitions() {
|
||||
return myNameToElementDefinitions;
|
||||
}
|
||||
|
||||
public Map<String, RuntimeResourceDefinition> getNameToResourceDefinition() {
|
||||
Map<String, RuntimeResourceDefinition> getNameToResourceDefinition() {
|
||||
return myNameToResourceDefinitions;
|
||||
}
|
||||
|
||||
public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() {
|
||||
return (myNameToResourceDefinitions);
|
||||
}
|
||||
|
||||
public Map<String, Class<? extends IBaseResource>> getNameToResourceType() {
|
||||
Map<String, Class<? extends IBaseResource>> getNameToResourceType() {
|
||||
return myNameToResourceType;
|
||||
}
|
||||
|
||||
public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
|
||||
RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
|
||||
return myRuntimeChildUndeclaredExtensionDefinition;
|
||||
}
|
||||
|
||||
|
@ -145,11 +137,10 @@ class ModelScanner {
|
|||
}
|
||||
|
||||
private boolean isStandardType(Class<? extends IBase> theClass) {
|
||||
boolean retVal = myVersionTypes.contains(theClass);
|
||||
return retVal;
|
||||
return myVersionTypes.contains(theClass);
|
||||
}
|
||||
|
||||
private void scan(Class<? extends IBase> theClass) throws ConfigurationException {
|
||||
void scan(Class<? extends IBase> theClass) throws ConfigurationException {
|
||||
BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass);
|
||||
if (existingDef != null) {
|
||||
return;
|
||||
|
@ -204,9 +195,6 @@ class ModelScanner {
|
|||
ourLog.debug("Scanning resource block class: {}", theClass.getName());
|
||||
|
||||
String resourceName = theClass.getCanonicalName();
|
||||
if (isBlank(resourceName)) {
|
||||
throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
|
||||
}
|
||||
|
||||
// Just in case someone messes up when upgrading from DSTU2
|
||||
if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
|
||||
|
@ -341,14 +329,14 @@ class ModelScanner {
|
|||
|
||||
private void scanResourceForSearchParams(Class<? extends IBaseResource> theClass, RuntimeResourceDefinition theResourceDef) {
|
||||
|
||||
Map<String, RuntimeSearchParam> nameToParam = new HashMap<String, RuntimeSearchParam>();
|
||||
Map<Field, SearchParamDefinition> compositeFields = new LinkedHashMap<Field, SearchParamDefinition>();
|
||||
Map<String, RuntimeSearchParam> nameToParam = new HashMap<>();
|
||||
Map<Field, SearchParamDefinition> compositeFields = new LinkedHashMap<>();
|
||||
|
||||
/*
|
||||
* Make sure we pick up fields in interfaces too.. This ensures that we
|
||||
* grab the _id field which generally gets picked up via interface
|
||||
*/
|
||||
Set<Field> fields = new HashSet<Field>(Arrays.asList(theClass.getFields()));
|
||||
Set<Field> fields = new HashSet<>(Arrays.asList(theClass.getFields()));
|
||||
Class<?> nextClass = theClass;
|
||||
do {
|
||||
for (Class<?> nextInterface : nextClass.getInterfaces()) {
|
||||
|
@ -400,12 +388,12 @@ class ModelScanner {
|
|||
for (Entry<Field, SearchParamDefinition> nextEntry : compositeFields.entrySet()) {
|
||||
SearchParamDefinition searchParam = nextEntry.getValue();
|
||||
|
||||
List<RuntimeSearchParam> compositeOf = new ArrayList<RuntimeSearchParam>();
|
||||
List<RuntimeSearchParam> compositeOf = new ArrayList<>();
|
||||
for (String nextName : searchParam.compositeOf()) {
|
||||
RuntimeSearchParam param = nameToParam.get(nextName);
|
||||
if (param == null) {
|
||||
ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}",
|
||||
new Object[]{theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()});
|
||||
theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet());
|
||||
continue;
|
||||
}
|
||||
compositeOf.add(param);
|
||||
|
@ -417,7 +405,7 @@ class ModelScanner {
|
|||
}
|
||||
|
||||
private Set<String> toTargetList(Class<? extends IBaseResource>[] theTarget) {
|
||||
HashSet<String> retVal = new HashSet<String>();
|
||||
HashSet<String> retVal = new HashSet<>();
|
||||
|
||||
for (Class<? extends IBaseResource> nextType : theTarget) {
|
||||
ResourceDef resourceDef = nextType.getAnnotation(ResourceDef.class);
|
||||
|
@ -486,7 +474,7 @@ class ModelScanner {
|
|||
}
|
||||
|
||||
static Set<Class<? extends IBase>> scanVersionPropertyFile(Set<Class<? extends IBase>> theDatatypes, Map<String, Class<? extends IBaseResource>> theResourceTypes, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingElementDefinitions) {
|
||||
Set<Class<? extends IBase>> retVal = new HashSet<Class<? extends IBase>>();
|
||||
Set<Class<? extends IBase>> retVal = new HashSet<>();
|
||||
|
||||
try (InputStream str = theVersion.getVersionImplementation().getFhirVersionPropertiesFile()) {
|
||||
Properties prop = new Properties();
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
package ca.uhn.fhir.context;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.model.api.annotation.ProvidesResources;
|
||||
|
||||
/**
|
||||
* Scans a class tagged with {@code ProvidesResources} and adds any resources listed to its FhirContext's resource
|
||||
* definition list. This makes the profile generator find the classes.
|
||||
*
|
||||
* @see ca.uhn.fhir.model.api.annotation.ProvidesResources
|
||||
*/
|
||||
public class ProvidedResourceScanner {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ProvidedResourceScanner.class);
|
||||
private FhirContext myContext;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theContext
|
||||
* - context whose resource definition list is to be updated by the scanner
|
||||
*/
|
||||
public ProvidedResourceScanner(FhirContext theContext) {
|
||||
myContext = theContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code theProvider} is tagged with the {@code ProvidesResources} annotation, this method will add every
|
||||
* resource listed by the {@code resources} method.
|
||||
* <p>
|
||||
* Notes:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>if {@code theProvider} isn't annotated with {@code resources} nothing is done; it's expected that most
|
||||
* RestfulServers and ResourceProviders won't be annotated.</li>
|
||||
* <li>any object listed in {@code resources} that doesn't implement {@code IResource} will generate a warning in the
|
||||
* log.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param theProvider
|
||||
* - Normally, either a {@link ca.uhn.fhir.rest.server.RestfulServer} or a
|
||||
* {@link ca.uhn.fhir.rest.server.IResourceProvider} that might be annotated with
|
||||
* {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void scanForProvidedResources(Object theProvider) {
|
||||
ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
|
||||
if (annotation == null)
|
||||
return;
|
||||
for (Class<?> clazz : annotation.resources()) {
|
||||
if (IBaseResource.class.isAssignableFrom(clazz)) {
|
||||
myContext.getResourceDefinition((Class<? extends IBaseResource>) clazz);
|
||||
} else {
|
||||
ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any metadata that was added by any {@code ProvidesResources} annotation
|
||||
* present in {@code theProvider}. This method is callled from {@code RestfulService}
|
||||
* when it is unregistering a Resource Provider.
|
||||
*
|
||||
* @param theProvider
|
||||
* - Normally a {@link ca.uhn.fhir.rest.server.IResourceProvider} that might
|
||||
* be annotated with {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
|
||||
*/
|
||||
public void removeProvidedResources(Object theProvider) {
|
||||
ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
|
||||
if (annotation == null)
|
||||
return;
|
||||
for (Class<?> clazz : annotation.resources()) {
|
||||
if (IBaseResource.class.isAssignableFrom(clazz)) {
|
||||
// TODO -- not currently used but should be finished for completeness
|
||||
} else {
|
||||
ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -102,6 +102,11 @@ public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST>
|
|||
*/
|
||||
boolean isCodeSystemSupported(FhirContext theContext, String theSystem);
|
||||
|
||||
/**
|
||||
* Fetch the given ValueSet by URL
|
||||
*/
|
||||
IBaseResource fetchValueSet(FhirContext theContext, String theValueSetUrl);
|
||||
|
||||
/**
|
||||
* Validates that the given code exists and if possible returns a display
|
||||
* name. This method is called to check codes which are found in "example"
|
||||
|
@ -112,7 +117,7 @@ public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST>
|
|||
* @param theDisplay The display name, if it should also be validated
|
||||
* @return Returns a validation result object
|
||||
*/
|
||||
CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
|
||||
CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl);
|
||||
|
||||
/**
|
||||
* Look up a code using the system and code value
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ package ca.uhn.fhir.model.api;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
|
||||
import ca.uhn.fhir.model.primitive.DecimalDt;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
|
||||
|
@ -29,7 +28,6 @@ import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
@ -92,29 +90,7 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
theResource.setUserData(DELETED_AT.name(), theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Denotes the search score which a given resource should match in a transaction. See the FHIR transaction definition for information about this. Corresponds to the value in
|
||||
* <code>Bundle.entry.score</code> in a Bundle resource.
|
||||
* <p>
|
||||
* Note that search URL is only used in FHIR DSTU2 and later.
|
||||
* </p>
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link DecimalDt}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<DecimalDt> ENTRY_SCORE = new ResourceMetadataKeyEnum<DecimalDt>("ENTRY_SCORE") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public DecimalDt get(IResource theResource) {
|
||||
return getDecimalFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ENTRY_SCORE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, DecimalDt theObject) {
|
||||
theResource.getResourceMetadata().put(ENTRY_SCORE, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* If present and populated with a {@link BundleEntrySearchModeEnum}, contains the "bundle entry search mode", which is the value of the status field in the Bundle entry containing this resource.
|
||||
* The value for this key corresponds to field <code>Bundle.entry.search.mode</code>. This value can be set to provide a status value of "include" for included resources being returned by a
|
||||
|
@ -187,67 +163,7 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
}
|
||||
|
||||
};
|
||||
/**
|
||||
* If present and populated with a string, provides the "alternate link" (the link element in the bundle entry with <code>rel="alternate"</code>). Server implementations may populate this with a
|
||||
* complete URL, in which case the URL will be placed as-is in the bundle. They may alternately specify a resource relative URL (e.g. "Patient/1243") in which case the server will convert this to
|
||||
* an absolute URL at runtime.
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link String}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<String> LINK_ALTERNATE = new ResourceMetadataKeyEnum<String>("LINK_ALTERNATE") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public String get(IResource theResource) {
|
||||
return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), LINK_ALTERNATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, String theObject) {
|
||||
theResource.getResourceMetadata().put(LINK_ALTERNATE, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* If present and populated with a string, provides the "search link" (the link element in the bundle entry with <code>rel="search"</code>). Server implementations may populate this with a
|
||||
* complete URL, in which case the URL will be placed as-is in the bundle. They may alternately specify a resource relative URL (e.g. "Patient?name=tester") in which case the server will convert
|
||||
* this to an absolute URL at runtime.
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link String}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<String> LINK_SEARCH = new ResourceMetadataKeyEnum<String>("LINK_SEARCH") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public String get(IResource theResource) {
|
||||
return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), LINK_SEARCH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, String theObject) {
|
||||
theResource.getResourceMetadata().put(LINK_SEARCH, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The value for this key represents a previous ID used to identify this resource. This key is currently only used internally during transaction method processing.
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link IdDt}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<IdDt> PREVIOUS_ID = new ResourceMetadataKeyEnum<IdDt>("PREVIOUS_ID") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public IdDt get(IResource theResource) {
|
||||
return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata(), PREVIOUS_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, IdDt theObject) {
|
||||
theResource.getResourceMetadata().put(PREVIOUS_ID, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The value for this key represents a {@link List} of profile IDs that this resource claims to conform to.
|
||||
* <p>
|
||||
|
@ -301,18 +217,8 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<BaseCodingDt> securityLabels = (List<BaseCodingDt>) obj;
|
||||
if (securityLabels.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return securityLabels;
|
||||
} catch (ClassCastException e) {
|
||||
throw new InternalErrorException("Found an object of type '" + obj.getClass().getCanonicalName() + "' in resource metadata for key SECURITY_LABELS - Expected "
|
||||
+ BaseCodingDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
//noinspection unchecked
|
||||
return (List<BaseCodingDt>) obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -337,14 +243,9 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
Object retValObj = theResource.getResourceMetadata().get(TAG_LIST);
|
||||
if (retValObj == null) {
|
||||
return null;
|
||||
} else if (retValObj instanceof TagList) {
|
||||
if (((TagList) retValObj).isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return (TagList) retValObj;
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + TAG_LIST.name() + " - Expected "
|
||||
+ TagList.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -352,25 +253,6 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
theResource.getResourceMetadata().put(TAG_LIST, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* If present and populated with a string (as an instance of {@link String}), this value contains the title for this resource, as supplied in any bundles containing the resource.
|
||||
* <p>
|
||||
* Values for this key are of type <b>{@link String}</b>
|
||||
* </p>
|
||||
*/
|
||||
public static final ResourceMetadataKeyEnum<String> TITLE = new ResourceMetadataKeyEnum<String>("TITLE") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public String get(IResource theResource) {
|
||||
return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), TITLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, String theObject) {
|
||||
theResource.getResourceMetadata().put(TITLE, theObject);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The value for this key is the bundle entry <b>Updated</b> time. This is defined by FHIR as "Last Updated for resource". This value is also used for populating the "Last-Modified" header in the
|
||||
* case of methods that return a single resource (read, vread, etc.)
|
||||
|
@ -398,7 +280,10 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
* <p>
|
||||
* Values for this key are of type <b>{@link String}</b>
|
||||
* </p>
|
||||
*
|
||||
* @deprecated The {@link IResource#getId()} resource ID will now be populated with the version ID via the {@link IdDt#getVersionIdPart()} method
|
||||
*/
|
||||
@Deprecated
|
||||
public static final ResourceMetadataKeyEnum<String> VERSION = new ResourceMetadataKeyEnum<String>("VERSION") {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
@ -426,7 +311,7 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
|
||||
@Override
|
||||
public IdDt get(IResource theResource) {
|
||||
return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata(), VERSION_ID);
|
||||
return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -474,32 +359,45 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
|
||||
public abstract void put(IResource theResource, T theObject);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myValue;
|
||||
public static abstract class ResourceMetadataKeySupportingAnyResource<T, T2> extends ResourceMetadataKeyEnum<T> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ResourceMetadataKeySupportingAnyResource(String theValue) {
|
||||
super(theValue);
|
||||
}
|
||||
|
||||
public abstract T2 get(IAnyResource theResource);
|
||||
|
||||
public abstract void put(IAnyResource theResource, T2 theObject);
|
||||
|
||||
}
|
||||
|
||||
private static DecimalDt getDecimalFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<DecimalDt> theKey) {
|
||||
Object retValObj = theResourceMetadata.get(theKey);
|
||||
if (retValObj == null) {
|
||||
return null;
|
||||
} else if (retValObj instanceof DecimalDt) {
|
||||
if (((DecimalDt) retValObj).isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return (DecimalDt) retValObj;
|
||||
} else if (retValObj instanceof String) {
|
||||
if (StringUtils.isBlank((String) retValObj)) {
|
||||
return null;
|
||||
}
|
||||
return new DecimalDt((String) retValObj);
|
||||
} else if (retValObj instanceof Double) {
|
||||
return new DecimalDt((Double) retValObj);
|
||||
public static final class ExtensionResourceMetadataKey extends ResourceMetadataKeyEnum<ExtensionDt> {
|
||||
public ExtensionResourceMetadataKey(String theUrl) {
|
||||
super(theUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionDt get(IResource theResource) {
|
||||
Object retValObj = theResource.getResourceMetadata().get(this);
|
||||
if (retValObj == null) {
|
||||
return null;
|
||||
} else if (retValObj instanceof ExtensionDt) {
|
||||
return (ExtensionDt) retValObj;
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName()
|
||||
+ "' in resource metadata for key " + this.name() + " - Expected "
|
||||
+ ExtensionDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, ExtensionDt theObject) {
|
||||
theResource.getResourceMetadata().put(this, theObject);
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
|
||||
+ InstantDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends Enum<?>> T getEnumFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<T> theKey, Class<T> theEnumType,
|
||||
IValueSetEnumBinder<T> theBinder) {
|
||||
|
@ -515,8 +413,8 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
+ InstantDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
private static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<?> theKey) {
|
||||
return toId(theKey, theResourceMetadata.get(theKey));
|
||||
private static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata) {
|
||||
return toId(ResourceMetadataKeyEnum.VERSION_ID, theResourceMetadata.get(ResourceMetadataKeyEnum.VERSION_ID));
|
||||
}
|
||||
|
||||
private static List<IdDt> getIdListFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<?> theKey) {
|
||||
|
@ -586,49 +484,11 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
|||
}
|
||||
return (IdDt) retValObj;
|
||||
} else if (retValObj instanceof Number) {
|
||||
return new IdDt(((Number) retValObj).toString());
|
||||
return new IdDt(retValObj.toString());
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
|
||||
+ IdDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
public static abstract class ResourceMetadataKeySupportingAnyResource<T, T2> extends ResourceMetadataKeyEnum<T> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ResourceMetadataKeySupportingAnyResource(String theValue) {
|
||||
super(theValue);
|
||||
}
|
||||
|
||||
public abstract T2 get(IAnyResource theResource);
|
||||
|
||||
public abstract void put(IAnyResource theResource, T2 theObject);
|
||||
|
||||
}
|
||||
|
||||
public static final class ExtensionResourceMetadataKey extends ResourceMetadataKeyEnum<ExtensionDt> {
|
||||
public ExtensionResourceMetadataKey(String url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionDt get(IResource theResource) {
|
||||
Object retValObj = theResource.getResourceMetadata().get(this);
|
||||
if (retValObj == null) {
|
||||
return null;
|
||||
} else if (retValObj instanceof ExtensionDt) {
|
||||
return (ExtensionDt) retValObj;
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName()
|
||||
+ "' in resource metadata for key " + this.name() + " - Expected "
|
||||
+ ExtensionDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, ExtensionDt theObject) {
|
||||
theResource.getResourceMetadata().put(this, theObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -168,18 +168,6 @@ public class Tag extends BaseElement implements IElement, IBaseCoding {
|
|||
return this;
|
||||
}
|
||||
|
||||
public String toHeaderValue() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(this.getTerm());
|
||||
if (isNotBlank(this.getLabel())) {
|
||||
b.append("; label=\"").append(this.getLabel()).append('"');
|
||||
}
|
||||
if (isNotBlank(this.getScheme())) {
|
||||
b.append("; scheme=\"").append(this.getScheme()).append('"');
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package ca.uhn.fhir.model.api.annotation;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* IResourceProvider and RestfulServer subclasses can use this annotation to designate which custom resources they can provide.
|
||||
* These resources will automatically be added to the resource list used for profile generation.
|
||||
* <pre>
|
||||
* Examples:
|
||||
* {@literal @}ProvidesResources(resource=CustomObservation.class)
|
||||
* class CustomObservationResourceProvider implements IResourceProvider{...}
|
||||
*
|
||||
* {@literal @}ProvidesResources(resource={CustomPatient.class,CustomObservation.class}){...}
|
||||
* class FhirServer extends RestfulServer
|
||||
* }
|
||||
* </pre>
|
||||
* Note that you needn't annotate both the IResourceProvider and the RestfulServer for a given resource; either one will suffice.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ProvidesResources {
|
||||
Class<?>[] resources();
|
||||
}
|
|
@ -36,5 +36,7 @@ import java.lang.annotation.Target;
|
|||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value=ElementType.METHOD)
|
||||
public @interface Metadata {
|
||||
// nothing for now
|
||||
|
||||
long cacheMillis() default 60 * 1000L;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
package ca.uhn.fhir.rest.api;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
/*
|
||||
|
@ -22,13 +29,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
||||
public class QualifiedParamList extends ArrayList<String> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -83,16 +83,16 @@ public class QualifiedParamList extends ArrayList<String> {
|
|||
prev = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (str.equals(",")) {
|
||||
if (countTrailingSlashes(prev) % 2 == 1) {
|
||||
int idx = retVal.size() - 1;
|
||||
String existing = retVal.get(idx);
|
||||
prev = existing.substring(0, existing.length() - 1) + ',';
|
||||
retVal.set(idx, prev);
|
||||
} else {
|
||||
prev = null;
|
||||
}
|
||||
if (countTrailingSlashes(prev) % 2 == 1) {
|
||||
int idx = retVal.size() - 1;
|
||||
String existing = retVal.get(idx);
|
||||
prev = existing.substring(0, existing.length() - 1) + ',';
|
||||
retVal.set(idx, prev);
|
||||
} else {
|
||||
prev = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -108,11 +108,18 @@ public class QualifiedParamList extends ArrayList<String> {
|
|||
|
||||
}
|
||||
|
||||
// If no value was found, at least add that empty string as a value. It should get ignored later, but at
|
||||
// least this lets us give a sensible error message if the parameter name was bad. See
|
||||
// ResourceProviderR4Test#testParameterWithNoValueThrowsError_InvalidChainOnCustomSearch for an example
|
||||
if (retVal.size() == 0) {
|
||||
retVal.add("");
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private static int countTrailingSlashes(String theString) {
|
||||
if(theString==null) {
|
||||
if (theString == null) {
|
||||
return 0;
|
||||
}
|
||||
int retVal = 0;
|
||||
|
|
|
@ -89,7 +89,7 @@ public enum RestSearchParameterTypeEnum {
|
|||
*/
|
||||
HAS("string", "http://hl7.org/fhir/search-param-type"),
|
||||
|
||||
/**
|
||||
/**
|
||||
* Code Value: <b>number</b>
|
||||
*
|
||||
* Search parameter SHALL be a number (a whole number, or a decimal).
|
||||
|
|
|
@ -23,10 +23,10 @@ package ca.uhn.fhir.rest.api.server;
|
|||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
/**
|
||||
* This interface is a parameter type for the {@link ca.uhn.fhir.interceptor.api.Pointcut#STORAGE_PRESHOW_RESOURCE}
|
||||
* This interface is a parameter type for the {@link ca.uhn.fhir.interceptor.api.Pointcut#STORAGE_PRESHOW_RESOURCES}
|
||||
* hook.
|
||||
*/
|
||||
public interface IPreResourceShowDetails {
|
||||
public interface IPreResourceShowDetails extends Iterable<IBaseResource> {
|
||||
|
||||
/**
|
||||
* @return Returns the number of resources being shown
|
||||
|
|
|
@ -24,9 +24,10 @@ import com.google.common.collect.Lists;
|
|||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class SimplePreResourceShowDetails implements IPreResourceShowDetails {
|
||||
public class SimplePreResourceShowDetails implements IPreResourceShowDetails, Iterable<IBaseResource> {
|
||||
|
||||
private final List<IBaseResource> myResources;
|
||||
private final boolean[] mySubSets;
|
||||
|
@ -64,4 +65,9 @@ public class SimplePreResourceShowDetails implements IPreResourceShowDetails {
|
|||
Validate.isTrue(theIndex < myResources.size(), "Invalid index {} - theIndex must be < %d", theIndex, myResources.size());
|
||||
mySubSets[theIndex] = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<IBaseResource> iterator() {
|
||||
return myResources.iterator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,12 +33,6 @@ import java.util.Map;
|
|||
*/
|
||||
public interface IHttpResponse {
|
||||
|
||||
/**
|
||||
* @deprecated This method was deprecated in HAPI FHIR 2.2 because its name has a typo. Use {@link #bufferEntity()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void bufferEntitity() throws IOException;
|
||||
|
||||
/**
|
||||
* Buffer the message entity data.
|
||||
* <p>
|
||||
|
|
|
@ -61,43 +61,43 @@ public class ParameterUtil {
|
|||
* This is a utility method intended provided to help the JPA module.
|
||||
*/
|
||||
public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RestSearchParameterTypeEnum paramType,
|
||||
String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
|
||||
String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
|
||||
QueryParameterAndBinder binder = null;
|
||||
switch (paramType) {
|
||||
case COMPOSITE:
|
||||
throw new UnsupportedOperationException();
|
||||
case DATE:
|
||||
binder = new QueryParameterAndBinder(DateAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case NUMBER:
|
||||
binder = new QueryParameterAndBinder(NumberAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case QUANTITY:
|
||||
binder = new QueryParameterAndBinder(QuantityAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case REFERENCE:
|
||||
binder = new QueryParameterAndBinder(ReferenceAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case STRING:
|
||||
binder = new QueryParameterAndBinder(StringAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case TOKEN:
|
||||
binder = new QueryParameterAndBinder(TokenAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case URI:
|
||||
binder = new QueryParameterAndBinder(UriAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case HAS:
|
||||
binder = new QueryParameterAndBinder(HasAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>> emptyList());
|
||||
break;
|
||||
case COMPOSITE:
|
||||
throw new UnsupportedOperationException();
|
||||
case DATE:
|
||||
binder = new QueryParameterAndBinder(DateAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case NUMBER:
|
||||
binder = new QueryParameterAndBinder(NumberAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case QUANTITY:
|
||||
binder = new QueryParameterAndBinder(QuantityAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case REFERENCE:
|
||||
binder = new QueryParameterAndBinder(ReferenceAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case STRING:
|
||||
binder = new QueryParameterAndBinder(StringAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case TOKEN:
|
||||
binder = new QueryParameterAndBinder(TokenAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case URI:
|
||||
binder = new QueryParameterAndBinder(UriAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
case HAS:
|
||||
binder = new QueryParameterAndBinder(HasAndListParam.class,
|
||||
Collections.<Class<? extends IQueryParameterType>>emptyList());
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME null access
|
||||
|
@ -108,7 +108,7 @@ public class ParameterUtil {
|
|||
* This is a utility method intended provided to help the JPA module.
|
||||
*/
|
||||
public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RuntimeSearchParam theParamDef,
|
||||
String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
|
||||
String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
|
||||
RestSearchParameterTypeEnum paramType = theParamDef.getParamType();
|
||||
return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters);
|
||||
}
|
||||
|
@ -126,14 +126,14 @@ public class ParameterUtil {
|
|||
for (int i = 0; i < theValue.length(); i++) {
|
||||
char next = theValue.charAt(i);
|
||||
switch (next) {
|
||||
case '$':
|
||||
case ',':
|
||||
case '|':
|
||||
case '\\':
|
||||
b.append('\\');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case '$':
|
||||
case ',':
|
||||
case '|':
|
||||
case '\\':
|
||||
b.append('\\');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
b.append(next);
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ public class ParameterUtil {
|
|||
|
||||
public static boolean isBindableIntegerType(Class<?> theClass) {
|
||||
return Integer.class.isAssignableFrom(theClass)
|
||||
|| IPrimitiveType.class.isAssignableFrom(theClass);
|
||||
|| IPrimitiveType.class.isAssignableFrom(theClass);
|
||||
}
|
||||
|
||||
public static String escapeAndJoinOrList(Collection<String> theValues) {
|
||||
|
@ -236,7 +236,7 @@ public class ParameterUtil {
|
|||
if (value.charAt(0) == '"') {
|
||||
eTagVersion = value.substring(1, value.length() - 1);
|
||||
} else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/'
|
||||
&& value.charAt(2) == '"') {
|
||||
&& value.charAt(2) == '"') {
|
||||
eTagVersion = value.substring(3, value.length() - 1);
|
||||
} else {
|
||||
eTagVersion = value;
|
||||
|
@ -262,16 +262,16 @@ public class ParameterUtil {
|
|||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName,
|
||||
QualifiedParamList theParameters) {
|
||||
QualifiedParamList theParameters) {
|
||||
if (theParameters.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (theParameters.size() > 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Type " + theParam.getClass().getCanonicalName() + " does not support multiple values");
|
||||
"Type " + theParam.getClass().getCanonicalName() + " does not support multiple values");
|
||||
}
|
||||
theParam.setValueAsQueryToken(theContext, theParamName, theParameters.getQualifier(),
|
||||
theParameters.get(0));
|
||||
theParameters.get(0));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -351,13 +351,13 @@ public class ParameterUtil {
|
|||
b.append(next);
|
||||
} else {
|
||||
switch (theValue.charAt(i + 1)) {
|
||||
case '$':
|
||||
case ',':
|
||||
case '|':
|
||||
case '\\':
|
||||
continue;
|
||||
default:
|
||||
b.append(next);
|
||||
case '$':
|
||||
case ',':
|
||||
case '|':
|
||||
case '\\':
|
||||
continue;
|
||||
default:
|
||||
b.append(next);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -248,26 +248,30 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ {
|
|||
|
||||
@Override
|
||||
public boolean equals(Object theO) {
|
||||
if (this == theO) return true;
|
||||
if (this == theO) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (theO == null || getClass() != theO.getClass()) return false;
|
||||
if (theO == null || getClass() != theO.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TokenParam that = (TokenParam) theO;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(myModifier, that.myModifier)
|
||||
.append(mySystem, that.mySystem)
|
||||
.append(myValue, that.myValue)
|
||||
.isEquals();
|
||||
EqualsBuilder b = new EqualsBuilder();
|
||||
b.append(myModifier, that.myModifier);
|
||||
b.append(mySystem, that.mySystem);
|
||||
b.append(myValue, that.myValue);
|
||||
return b.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(myModifier)
|
||||
.append(mySystem)
|
||||
.append(myValue)
|
||||
.toHashCode();
|
||||
HashCodeBuilder b = new HashCodeBuilder(17, 37);
|
||||
b.append(myModifier);
|
||||
b.append(mySystem);
|
||||
b.append(myValue);
|
||||
return b.toHashCode();
|
||||
}
|
||||
|
||||
private static String toSystemValue(UriDt theSystem) {
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AsyncUtil {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(AsyncUtil.class);
|
||||
|
||||
/**
|
||||
* Non instantiable
|
||||
*/
|
||||
private AsyncUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Thread.sleep and if an InterruptedException occurs, logs a warning but otherwise continues
|
||||
*
|
||||
* @param theMillis The number of millis to sleep
|
||||
* @return Did we sleep the whole amount
|
||||
*/
|
||||
public static boolean sleep(long theMillis) {
|
||||
try {
|
||||
Thread.sleep(theMillis);
|
||||
return true;
|
||||
} catch (InterruptedException theE) {
|
||||
Thread.currentThread().interrupt();
|
||||
ourLog.warn("Sleep for {}ms was interrupted", theMillis);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean awaitLatchAndThrowInternalErrorExceptionOnInterrupt(CountDownLatch theInitialCollectionLatch, long theTime, TimeUnit theTimeUnit) {
|
||||
try {
|
||||
return theInitialCollectionLatch.await(theTime, theTimeUnit);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean awaitLatchAndIgnoreInterrupt(CountDownLatch theInitialCollectionLatch, long theTime, TimeUnit theTimeUnit) {
|
||||
try {
|
||||
return theInitialCollectionLatch.await(theTime, theTimeUnit);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
ourLog.warn("Interrupted while waiting for latch");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,9 +20,8 @@ package ca.uhn.fhir.util;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.*;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.ICompositeType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -35,12 +34,10 @@ public class AttachmentUtil {
|
|||
* Fetches the base64Binary value of Attachment.data, creating it if it does not
|
||||
* already exist.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static IPrimitiveType<byte[]> getOrCreateData(FhirContext theContext, ICompositeType theAttachment) {
|
||||
return getOrCreateChild(theContext, theAttachment, "data", "base64Binary");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static IPrimitiveType<String> getOrCreateContentType(FhirContext theContext, ICompositeType theAttachment) {
|
||||
return getOrCreateChild(theContext, theAttachment, "contentType", "string");
|
||||
}
|
||||
|
@ -64,6 +61,16 @@ public class AttachmentUtil {
|
|||
});
|
||||
}
|
||||
|
||||
public static void setUrl(FhirContext theContext, ICompositeType theAttachment, String theUrl) {
|
||||
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "url");
|
||||
assert entryChild != null : "Version " + theContext + " has no child " + "url";
|
||||
String typeName = "uri";
|
||||
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
|
||||
typeName = "url";
|
||||
}
|
||||
entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, typeName, theUrl));
|
||||
}
|
||||
|
||||
public static void setContentType(FhirContext theContext, ICompositeType theAttachment, String theContentType) {
|
||||
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "contentType");
|
||||
entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, "code", theContentType));
|
||||
|
@ -88,7 +95,9 @@ public class AttachmentUtil {
|
|||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> IPrimitiveType<T> newPrimitive(FhirContext theContext, String theType, T theValue) {
|
||||
IPrimitiveType<T> primitive = (IPrimitiveType<T>) theContext.getElementDefinition(theType).newInstance();
|
||||
BaseRuntimeElementDefinition<?> elementDefinition = theContext.getElementDefinition(theType);
|
||||
Validate.notNull(elementDefinition, "Unknown type %s for %s", theType, theContext);
|
||||
IPrimitiveType<T> primitive = (IPrimitiveType<T>) elementDefinition.newInstance();
|
||||
primitive.setValue(theValue);
|
||||
return primitive;
|
||||
}
|
||||
|
@ -100,4 +109,8 @@ public class AttachmentUtil {
|
|||
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theElement.getClass());
|
||||
return def.getChildByName(theName);
|
||||
}
|
||||
|
||||
public static ICompositeType newInstance(FhirContext theFhirCtx) {
|
||||
return (ICompositeType) theFhirCtx.getElementDefinition("Attachment").newInstance();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -738,25 +738,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class FileUtil {
|
||||
// Use "bytes" instead of just "b" because it reads easier in logs
|
||||
private static final String[] UNITS = new String[]{"Bytes", "kB", "MB", "GB", "TB"};
|
||||
|
||||
public static String formatFileSize(long theBytes) {
|
||||
if (theBytes <= 0) {
|
||||
return "0 " + UNITS[0];
|
||||
}
|
||||
int digitGroups = (int) (Math.log10(theBytes) / Math.log10(1024));
|
||||
digitGroups = Math.min(digitGroups, UNITS.length - 1);
|
||||
return new DecimalFormat("###0.#").format(theBytes / Math.pow(1024, digitGroups)) + " " + UNITS[digitGroups];
|
||||
}
|
||||
|
||||
}
|
|
@ -45,7 +45,7 @@ public interface IModelVisitor2 {
|
|||
/**
|
||||
*
|
||||
*/
|
||||
boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath);
|
||||
default boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { return true; }
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ package ca.uhn.fhir.util;
|
|||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -46,6 +46,29 @@ public class MetaUtil {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for <code>Resource.meta.source</code> for R4+ resources, and places the value in
|
||||
* an extension on <code>Resource.meta</code>
|
||||
* with the URL <code>http://hapifhir.io/fhir/StructureDefinition/resource-meta-source</code> for DSTU3.
|
||||
*
|
||||
* @param theContext The FhirContext object
|
||||
* @param theResource The resource to modify
|
||||
* @param theValue The source URI
|
||||
* @see <a href="http://hl7.org/fhir/resource-definitions.html#Resource.meta">Meta.source</a>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void setSource(FhirContext theContext, IBaseResource theResource, String theValue) {
|
||||
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
|
||||
MetaUtil.setSource(theContext, theResource.getMeta(), theValue);
|
||||
} else if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
|
||||
IBaseExtension<?, ?> sourceExtension = ((IBaseHasExtensions) theResource.getMeta()).addExtension();
|
||||
sourceExtension.setUrl(Constants.EXT_META_SOURCE);
|
||||
IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("uri").newInstance();
|
||||
value.setValue(theValue);
|
||||
sourceExtension.setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSource(FhirContext theContext, IBaseMetaType theMeta, String theValue) {
|
||||
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theMeta.getClass());
|
||||
BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");
|
||||
|
|
|
@ -19,22 +19,17 @@ package ca.uhn.fhir.util;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/**
|
||||
* Utilities for dealing with OperationOutcome resources across various model versions
|
||||
|
@ -43,20 +38,17 @@ public class OperationOutcomeUtil {
|
|||
|
||||
/**
|
||||
* Add an issue to an OperationOutcome
|
||||
*
|
||||
* @param theCtx
|
||||
* The fhir context
|
||||
* @param theOperationOutcome
|
||||
* The OO resource to add to
|
||||
* @param theSeverity
|
||||
* The severity (fatal | error | warning | information)
|
||||
* @param theDetails
|
||||
* The details string
|
||||
* @param theCtx The fhir context
|
||||
* @param theOperationOutcome The OO resource to add to
|
||||
* @param theSeverity The severity (fatal | error | warning | information)
|
||||
* @param theDetails The details string
|
||||
* @param theCode
|
||||
* @return Returns the newly added issue
|
||||
*/
|
||||
public static void addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) {
|
||||
public static IBase addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) {
|
||||
IBase issue = createIssue(theCtx, theOperationOutcome);
|
||||
populateDetails(theCtx, issue, theSeverity, theDetails, theLocation, theCode);
|
||||
return issue;
|
||||
}
|
||||
|
||||
private static IBase createIssue(FhirContext theCtx, IBaseResource theOutcome) {
|
||||
|
@ -140,7 +132,6 @@ public class OperationOutcomeUtil {
|
|||
|
||||
BaseRuntimeElementDefinition<?> stringDef = detailsChild.getChildByName(detailsChild.getElementName());
|
||||
BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity");
|
||||
BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location");
|
||||
|
||||
IPrimitiveType<?> severityElem = (IPrimitiveType<?>) severityChild.getChildByName("severity").newInstance(severityChild.getInstanceConstructorArguments());
|
||||
severityElem.setValueAsString(theSeverity);
|
||||
|
@ -150,7 +141,13 @@ public class OperationOutcomeUtil {
|
|||
string.setValueAsString(theDetails);
|
||||
detailsChild.getMutator().setValue(theIssue, string);
|
||||
|
||||
addLocationToIssue(theCtx, theIssue, theLocation);
|
||||
}
|
||||
|
||||
public static void addLocationToIssue(FhirContext theContext, IBase theIssue, String theLocation) {
|
||||
if (isNotBlank(theLocation)) {
|
||||
BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theIssue.getClass());
|
||||
BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location");
|
||||
IPrimitiveType<?> locationElem = (IPrimitiveType<?>) locationChild.getChildByName("location").newInstance(locationChild.getInstanceConstructorArguments());
|
||||
locationElem.setValueAsString(theLocation);
|
||||
locationChild.getMutator().addValue(theIssue, locationElem);
|
||||
|
|
|
@ -72,6 +72,8 @@ public class ParametersUtil {
|
|||
}
|
||||
|
||||
private static void addClientParameter(FhirContext theContext, Object theValue, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem, String theName) {
|
||||
Validate.notNull(theValue, "theValue must not be null");
|
||||
|
||||
if (theValue instanceof IBaseResource) {
|
||||
IBase parameter = createParameterRepetition(theContext, theTargetResource, paramChild, paramChildElem, theName);
|
||||
paramChildElem.getChildByName("resource").getMutator().addValue(parameter, (IBaseResource) theValue);
|
||||
|
@ -162,7 +164,6 @@ public class ParametersUtil {
|
|||
IPrimitiveType<Boolean> value = (IPrimitiveType<Boolean>) theCtx.getElementDefinition("boolean").newInstance();
|
||||
value.setValue(theValue);
|
||||
addParameterToParameters(theCtx, theParameters, theName, value);
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -51,16 +51,8 @@ public class ReflectionUtil {
|
|||
}
|
||||
|
||||
public static Class<?> getGenericCollectionTypeOfField(Field next) {
|
||||
Class<?> type;
|
||||
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
|
||||
Type firstArg = collectionType.getActualTypeArguments()[0];
|
||||
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
|
||||
ParameterizedType pt = ((ParameterizedType) firstArg);
|
||||
type = (Class<?>) pt.getRawType();
|
||||
} else {
|
||||
type = (Class<?>) firstArg;
|
||||
}
|
||||
return type;
|
||||
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -84,42 +76,37 @@ public class ReflectionUtil {
|
|||
}
|
||||
|
||||
public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) {
|
||||
Class<?> type;
|
||||
Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex];
|
||||
if (Class.class.equals(genericParameterType) || Class.class.equals(genericParameterType.getClass())) {
|
||||
return null;
|
||||
}
|
||||
ParameterizedType collectionType = (ParameterizedType) genericParameterType;
|
||||
Type firstArg = collectionType.getActualTypeArguments()[0];
|
||||
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
|
||||
ParameterizedType pt = ((ParameterizedType) firstArg);
|
||||
type = (Class<?>) pt.getRawType();
|
||||
} else {
|
||||
type = (Class<?>) firstArg;
|
||||
}
|
||||
return type;
|
||||
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
public static Class<?> getGenericCollectionTypeOfMethodReturnType(Method theMethod) {
|
||||
Class<?> type;
|
||||
Type genericReturnType = theMethod.getGenericReturnType();
|
||||
if (!(genericReturnType instanceof ParameterizedType)) {
|
||||
return null;
|
||||
}
|
||||
ParameterizedType collectionType = (ParameterizedType) genericReturnType;
|
||||
Type firstArg = collectionType.getActualTypeArguments()[0];
|
||||
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
|
||||
ParameterizedType pt = ((ParameterizedType) firstArg);
|
||||
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
private static Class<?> getGenericCollectionTypeOf(Type theType) {
|
||||
Class<?> type;
|
||||
if (ParameterizedType.class.isAssignableFrom(theType.getClass())) {
|
||||
ParameterizedType pt = ((ParameterizedType) theType);
|
||||
type = (Class<?>) pt.getRawType();
|
||||
} else if (firstArg instanceof TypeVariable<?>) {
|
||||
Type decl = ((TypeVariable) firstArg).getBounds()[0];
|
||||
} else if (theType instanceof TypeVariable<?>) {
|
||||
Type decl = ((TypeVariable) theType).getBounds()[0];
|
||||
return (Class<?>) decl;
|
||||
} else if (firstArg instanceof WildcardType) {
|
||||
Type decl = ((WildcardType) firstArg).getUpperBounds()[0];
|
||||
} else if (theType instanceof WildcardType) {
|
||||
Type decl = ((WildcardType) theType).getUpperBounds()[0];
|
||||
return (Class<?>) decl;
|
||||
} else {
|
||||
type = (Class<?>) firstArg;
|
||||
type = (Class<?>) theType;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
@ -154,8 +141,7 @@ public class ReflectionUtil {
|
|||
public static Object newInstanceOfFhirServerType(String theType) {
|
||||
String errorMessage = "Unable to instantiate server framework. Please make sure that hapi-fhir-server library is on your classpath!";
|
||||
String wantedType = "ca.uhn.fhir.rest.api.server.IFhirVersionServer";
|
||||
Object fhirServerVersion = newInstanceOfType(theType, errorMessage, wantedType);
|
||||
return fhirServerVersion;
|
||||
return newInstanceOfType(theType, errorMessage, wantedType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
@ -9,8 +10,6 @@ import java.util.Date;
|
|||
import java.util.LinkedList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
|
@ -48,12 +47,14 @@ public class StopWatch {
|
|||
private long myStarted = now();
|
||||
private TaskTiming myCurrentTask;
|
||||
private LinkedList<TaskTiming> myTasks;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public StopWatch() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -63,7 +64,13 @@ public class StopWatch {
|
|||
myStarted = theStart.getTime();
|
||||
}
|
||||
|
||||
public StopWatch(long theL) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theStart The time that the stopwatch was started
|
||||
*/
|
||||
public StopWatch(long theStart) {
|
||||
myStarted = theStart;
|
||||
}
|
||||
|
||||
private void addNewlineIfContentExists(StringBuilder theB) {
|
||||
|
@ -120,6 +127,8 @@ public class StopWatch {
|
|||
b.append(": ");
|
||||
b.append(formatMillis(delta));
|
||||
}
|
||||
} else {
|
||||
b.append("No tasks");
|
||||
}
|
||||
|
||||
TaskTiming last = null;
|
||||
|
@ -257,12 +266,11 @@ public class StopWatch {
|
|||
*/
|
||||
public void startTask(String theTaskName) {
|
||||
endCurrentTask();
|
||||
if (isNotBlank(theTaskName)) {
|
||||
myCurrentTask = new TaskTiming()
|
||||
.setTaskName(theTaskName)
|
||||
.setStart(now());
|
||||
myTasks.add(myCurrentTask);
|
||||
}
|
||||
Validate.notBlank(theTaskName, "Task name must not be blank");
|
||||
myCurrentTask = new TaskTiming()
|
||||
.setTaskName(theTaskName)
|
||||
.setStart(now());
|
||||
myTasks.add(myCurrentTask);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -331,18 +339,18 @@ public class StopWatch {
|
|||
/**
|
||||
* Append a right-aligned and zero-padded numeric value to a `StringBuilder`.
|
||||
*/
|
||||
static private void append(StringBuilder tgt, String pfx, int dgt, long val) {
|
||||
tgt.append(pfx);
|
||||
if (dgt > 1) {
|
||||
int pad = (dgt - 1);
|
||||
for (long xa = val; xa > 9 && pad > 0; xa /= 10) {
|
||||
static void appendRightAlignedNumber(StringBuilder theStringBuilder, String thePrefix, int theNumberOfDigits, long theValueToAppend) {
|
||||
theStringBuilder.append(thePrefix);
|
||||
if (theNumberOfDigits > 1) {
|
||||
int pad = (theNumberOfDigits - 1);
|
||||
for (long xa = theValueToAppend; xa > 9 && pad > 0; xa /= 10) {
|
||||
pad--;
|
||||
}
|
||||
for (int xa = 0; xa < pad; xa++) {
|
||||
tgt.append('0');
|
||||
theStringBuilder.append('0');
|
||||
}
|
||||
}
|
||||
tgt.append(val);
|
||||
theStringBuilder.append(theValueToAppend);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -399,11 +407,11 @@ public class StopWatch {
|
|||
}
|
||||
} else {
|
||||
long millisAsLong = (long) theMillis;
|
||||
append(buf, "", 2, ((millisAsLong % DateUtils.MILLIS_PER_DAY) / DateUtils.MILLIS_PER_HOUR));
|
||||
append(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_HOUR) / DateUtils.MILLIS_PER_MINUTE));
|
||||
append(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_MINUTE) / DateUtils.MILLIS_PER_SECOND));
|
||||
appendRightAlignedNumber(buf, "", 2, ((millisAsLong % DateUtils.MILLIS_PER_DAY) / DateUtils.MILLIS_PER_HOUR));
|
||||
appendRightAlignedNumber(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_HOUR) / DateUtils.MILLIS_PER_MINUTE));
|
||||
appendRightAlignedNumber(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_MINUTE) / DateUtils.MILLIS_PER_SECOND));
|
||||
if (theMillis <= DateUtils.MILLIS_PER_MINUTE) {
|
||||
append(buf, ".", 3, (millisAsLong % DateUtils.MILLIS_PER_SECOND));
|
||||
appendRightAlignedNumber(buf, ".", 3, (millisAsLong % DateUtils.MILLIS_PER_SECOND));
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,13 @@ import com.ctc.wstx.stax.WstxOutputFactory;
|
|||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.codehaus.stax2.XMLOutputFactory2;
|
||||
import org.codehaus.stax2.io.EscapingWriterFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.stream.*;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
import java.io.*;
|
||||
|
@ -40,7 +46,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
|
||||
/**
|
||||
* Utility methods for working with the StAX API.
|
||||
*
|
||||
* <p>
|
||||
* This class contains code adapted from the Apache Axiom project.
|
||||
*/
|
||||
public class XmlUtil {
|
||||
|
@ -1503,10 +1509,77 @@ public class XmlUtil {
|
|||
validEntityNames.put("zscr", 0x1D4CF);
|
||||
validEntityNames.put("zwj", 0x0200D);
|
||||
validEntityNames.put("zwnj", 0x0200C);
|
||||
|
||||
|
||||
VALID_ENTITY_NAMES = Collections.unmodifiableMap(validEntityNames);
|
||||
}
|
||||
|
||||
/** Non-instantiable */
|
||||
private XmlUtil() {}
|
||||
|
||||
private static final class ExtendedEntityReplacingXmlResolver implements XMLResolver {
|
||||
@Override
|
||||
public Object resolveEntity(String thePublicID, String theSystemID, String theBaseURI, String theNamespace) {
|
||||
if (thePublicID == null && theSystemID == null) {
|
||||
if (theNamespace != null && VALID_ENTITY_NAMES.containsKey(theNamespace)) {
|
||||
return new String(Character.toChars(VALID_ENTITY_NAMES.get(theNamespace)));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyEscaper implements EscapingWriterFactory {
|
||||
|
||||
@Override
|
||||
public Writer createEscapingWriterFor(OutputStream theOut, String theEnc) throws UnsupportedEncodingException {
|
||||
return createEscapingWriterFor(new OutputStreamWriter(theOut, theEnc), theEnc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer createEscapingWriterFor(final Writer theW, String theEnc) {
|
||||
return new Writer() {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
theW.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
theW.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char[] theCbuf, int theOff, int theLen) throws IOException {
|
||||
boolean hasEscapable = false;
|
||||
for (int i = 0; i < theLen && !hasEscapable; i++) {
|
||||
char nextChar = theCbuf[i + theOff];
|
||||
switch (nextChar) {
|
||||
case '<':
|
||||
case '>':
|
||||
case '"':
|
||||
case '&':
|
||||
hasEscapable = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasEscapable) {
|
||||
theW.write(theCbuf, theOff, theLen);
|
||||
return;
|
||||
}
|
||||
|
||||
String escaped = StringEscapeUtils.escapeXml10(new String(theCbuf, theOff, theLen));
|
||||
theW.write(escaped.toCharArray());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static XMLOutputFactory createOutputFactory() throws FactoryConfigurationError {
|
||||
try {
|
||||
// Detect if we're running with the Android lib, and force repackaged Woodstox to be used
|
||||
|
@ -1637,15 +1710,11 @@ public class XmlUtil {
|
|||
try {
|
||||
Class.forName("com.ctc.wstx.stax.WstxInputFactory");
|
||||
boolean isWoodstox = inputFactory instanceof com.ctc.wstx.stax.WstxInputFactory;
|
||||
if ( !isWoodstox )
|
||||
{
|
||||
if (!isWoodstox) {
|
||||
// Check if implementation is woodstox by property since instanceof check does not work if running in JBoss
|
||||
try
|
||||
{
|
||||
isWoodstox = inputFactory.getProperty( "org.codehaus.stax2.implVersion" ) != null;
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
try {
|
||||
isWoodstox = inputFactory.getProperty("org.codehaus.stax2.implVersion") != null;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
@ -1673,7 +1742,6 @@ public class XmlUtil {
|
|||
return ourOutputFactory;
|
||||
}
|
||||
|
||||
|
||||
private static void logStaxImplementation(Class<?> theClass) {
|
||||
IDependencyLog logger = DependencyLogFactory.createJarLogger();
|
||||
if (logger != null) {
|
||||
|
@ -1682,7 +1750,6 @@ public class XmlUtil {
|
|||
ourHaveLoggedStaxImplementation = true;
|
||||
}
|
||||
|
||||
|
||||
static XMLInputFactory newInputFactory() throws FactoryConfigurationError {
|
||||
XMLInputFactory inputFactory;
|
||||
try {
|
||||
|
@ -1761,74 +1828,36 @@ public class XmlUtil {
|
|||
private static void throwUnitTestExceptionIfConfiguredToDoSo() throws FactoryConfigurationError, XMLStreamException {
|
||||
if (ourNextException != null) {
|
||||
if (ourNextException instanceof javax.xml.stream.FactoryConfigurationError) {
|
||||
throw ((javax.xml.stream.FactoryConfigurationError)ourNextException);
|
||||
throw ((javax.xml.stream.FactoryConfigurationError) ourNextException);
|
||||
}
|
||||
throw (XMLStreamException)ourNextException;
|
||||
throw (XMLStreamException) ourNextException;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ExtendedEntityReplacingXmlResolver implements XMLResolver {
|
||||
@Override
|
||||
public Object resolveEntity(String thePublicID, String theSystemID, String theBaseURI, String theNamespace) {
|
||||
if (thePublicID == null && theSystemID == null) {
|
||||
if (theNamespace != null && VALID_ENTITY_NAMES.containsKey(theNamespace)) {
|
||||
return new String(Character.toChars(VALID_ENTITY_NAMES.get(theNamespace)));
|
||||
}
|
||||
public static Document parseDocument(String theInput) throws IOException, SAXException {
|
||||
DocumentBuilder builder = null;
|
||||
try {
|
||||
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
docBuilderFactory.setNamespaceAware(true);
|
||||
docBuilderFactory.setXIncludeAware(false);
|
||||
docBuilderFactory.setExpandEntityReferences(false);
|
||||
try {
|
||||
docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
docBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
|
||||
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
docBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
throwUnitTestExceptionIfConfiguredToDoSo();
|
||||
} catch (Exception e) {
|
||||
ourLog.warn("Failed to set feature on XML parser: " + e.toString());
|
||||
}
|
||||
|
||||
return null;
|
||||
builder = docBuilderFactory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
|
||||
InputSource src = new InputSource(new StringReader(theInput));
|
||||
return builder.parse(src);
|
||||
}
|
||||
|
||||
public static class MyEscaper implements EscapingWriterFactory {
|
||||
|
||||
@Override
|
||||
public Writer createEscapingWriterFor(OutputStream theOut, String theEnc) throws UnsupportedEncodingException {
|
||||
return createEscapingWriterFor(new OutputStreamWriter(theOut, theEnc), theEnc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer createEscapingWriterFor(final Writer theW, String theEnc) {
|
||||
return new Writer() {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
theW.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
theW.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char[] theCbuf, int theOff, int theLen) throws IOException {
|
||||
boolean hasEscapable = false;
|
||||
for (int i = 0; i < theLen && !hasEscapable; i++) {
|
||||
char nextChar = theCbuf[i + theOff];
|
||||
switch (nextChar) {
|
||||
case '<':
|
||||
case '>':
|
||||
case '"':
|
||||
case '&':
|
||||
hasEscapable = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasEscapable) {
|
||||
theW.write(theCbuf, theOff, theLen);
|
||||
return;
|
||||
}
|
||||
|
||||
String escaped = StringEscapeUtils.escapeXml10(new String(theCbuf, theOff, theLen));
|
||||
theW.write(escaped.toCharArray());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -19,26 +19,32 @@ package ca.uhn.fhir.validation;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import javax.xml.validation.*;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.BOMInputStream;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.w3c.dom.ls.LSInput;
|
||||
import org.w3c.dom.ls.LSResourceResolver;
|
||||
import org.xml.sax.*;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.BOMInputStream;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.w3c.dom.ls.LSInput;
|
||||
import org.w3c.dom.ls.LSResourceResolver;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXNotRecognizedException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import javax.xml.validation.Schema;
|
||||
import javax.xml.validation.SchemaFactory;
|
||||
import javax.xml.validation.Validator;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
public class SchemaBaseValidator implements IValidatorModule {
|
||||
public static final String RESOURCES_JAR_NOTE = "Note that as of HAPI FHIR 1.2, DSTU2 validation files are kept in a separate JAR (hapi-fhir-validation-resources-XXX.jar) which must be added to your classpath. See the HAPI FHIR download page for more information.";
|
||||
|
@ -47,7 +53,7 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
private static final Set<String> SCHEMA_NAMES;
|
||||
|
||||
static {
|
||||
HashSet<String> sn = new HashSet<String>();
|
||||
HashSet<String> sn = new HashSet<>();
|
||||
sn.add("xml.xsd");
|
||||
sn.add("xhtml1-strict.xsd");
|
||||
sn.add("fhir-single.xsd");
|
||||
|
@ -59,15 +65,15 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
SCHEMA_NAMES = Collections.unmodifiableSet(sn);
|
||||
}
|
||||
|
||||
private Map<String, Schema> myKeyToSchema = new HashMap<String, Schema>();
|
||||
private final Map<String, Schema> myKeyToSchema = new HashMap<>();
|
||||
private FhirContext myCtx;
|
||||
|
||||
public SchemaBaseValidator(FhirContext theContext) {
|
||||
myCtx = theContext;
|
||||
}
|
||||
|
||||
private void doValidate(IValidationContext<?> theContext, String schemaName) {
|
||||
Schema schema = loadSchema("dstu", schemaName);
|
||||
private void doValidate(IValidationContext<?> theContext) {
|
||||
Schema schema = loadSchema();
|
||||
|
||||
try {
|
||||
Validator validator = schema.newValidator();
|
||||
|
@ -81,14 +87,14 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/339
|
||||
* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
|
||||
*/
|
||||
/*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/339
|
||||
* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
|
||||
*/
|
||||
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
||||
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
|
||||
}catch (SAXNotRecognizedException ex){
|
||||
ourLog.warn("Jaxp 1.5 Support not found.",ex);
|
||||
} catch (SAXNotRecognizedException ex) {
|
||||
ourLog.warn("Jaxp 1.5 Support not found.", ex);
|
||||
}
|
||||
|
||||
validator.validate(new StreamSource(new StringReader(encodedResource)));
|
||||
|
@ -99,17 +105,14 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
message.setMessage(e.getLocalizedMessage());
|
||||
message.setSeverity(ResultSeverityEnum.FATAL);
|
||||
theContext.addValidationMessage(message);
|
||||
} catch (SAXException e) {
|
||||
// Catch all
|
||||
throw new ConfigurationException("Could not load/parse schema file", e);
|
||||
} catch (IOException e) {
|
||||
} catch (SAXException | IOException e) {
|
||||
// Catch all
|
||||
throw new ConfigurationException("Could not load/parse schema file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Schema loadSchema(String theVersion, String theSchemaName) {
|
||||
String key = theVersion + "-" + theSchemaName;
|
||||
private Schema loadSchema() {
|
||||
String key = "fhir-single.xsd";
|
||||
|
||||
synchronized (myKeyToSchema) {
|
||||
Schema schema = myKeyToSchema.get(key);
|
||||
|
@ -117,81 +120,52 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
return schema;
|
||||
}
|
||||
|
||||
Source baseSource = loadXml(null, theSchemaName);
|
||||
Source baseSource = loadXml("fhir-single.xsd");
|
||||
|
||||
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
||||
schemaFactory.setResourceResolver(new MyResourceResolver());
|
||||
|
||||
try {
|
||||
try {
|
||||
/*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/339
|
||||
* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
|
||||
*/
|
||||
/*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/339
|
||||
* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
|
||||
*/
|
||||
schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
||||
}catch (SAXNotRecognizedException snex){
|
||||
ourLog.warn("Jaxp 1.5 Support not found.",snex);
|
||||
} catch (SAXNotRecognizedException e) {
|
||||
ourLog.warn("Jaxp 1.5 Support not found.", e);
|
||||
}
|
||||
schema = schemaFactory.newSchema(new Source[] { baseSource });
|
||||
schema = schemaFactory.newSchema(new Source[]{baseSource});
|
||||
} catch (SAXException e) {
|
||||
throw new ConfigurationException("Could not load/parse schema file: " + theSchemaName, e);
|
||||
throw new ConfigurationException("Could not load/parse schema file: " + "fhir-single.xsd", e);
|
||||
}
|
||||
myKeyToSchema.put(key, schema);
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
|
||||
private Source loadXml(String theSystemId, String theSchemaName) {
|
||||
Source loadXml(String theSchemaName) {
|
||||
String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theSchemaName;
|
||||
ourLog.debug("Going to load resource: {}", pathToBase);
|
||||
InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase);
|
||||
if (baseIs == null) {
|
||||
throw new InternalErrorException("Schema not found. " + RESOURCES_JAR_NOTE);
|
||||
try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) {
|
||||
if (baseIs == null) {
|
||||
throw new InternalErrorException("Schema not found. " + RESOURCES_JAR_NOTE);
|
||||
}
|
||||
try (BOMInputStream bomInputStream = new BOMInputStream(baseIs, false)) {
|
||||
try (InputStreamReader baseReader = new InputStreamReader(bomInputStream, StandardCharsets.UTF_8)) {
|
||||
// Buffer so that we can close the input stream
|
||||
String contents = IOUtils.toString(baseReader);
|
||||
return new StreamSource(new StringReader(contents), null);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
baseIs = new BOMInputStream(baseIs, false);
|
||||
InputStreamReader baseReader = new InputStreamReader(baseIs, Charset.forName("UTF-8"));
|
||||
Source baseSource = new StreamSource(baseReader, theSystemId);
|
||||
//FIXME resource leak
|
||||
return baseSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateResource(IValidationContext<IBaseResource> theContext) {
|
||||
doValidate(theContext, "fhir-single.xsd");
|
||||
}
|
||||
|
||||
private static class MyErrorHandler implements org.xml.sax.ErrorHandler {
|
||||
|
||||
private IValidationContext<?> myContext;
|
||||
|
||||
public MyErrorHandler(IValidationContext<?> theContext) {
|
||||
myContext = theContext;
|
||||
}
|
||||
|
||||
private void addIssue(SAXParseException theException, ResultSeverityEnum theSeverity) {
|
||||
SingleValidationMessage message = new SingleValidationMessage();
|
||||
message.setLocationLine(theException.getLineNumber());
|
||||
message.setLocationCol(theException.getColumnNumber());
|
||||
message.setMessage(theException.getLocalizedMessage());
|
||||
message.setSeverity(theSeverity);
|
||||
myContext.addValidationMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.FATAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.WARNING);
|
||||
}
|
||||
|
||||
doValidate(theContext);
|
||||
}
|
||||
|
||||
private final class MyResourceResolver implements LSResourceResolver {
|
||||
|
@ -225,4 +199,38 @@ public class SchemaBaseValidator implements IValidatorModule {
|
|||
}
|
||||
}
|
||||
|
||||
private static class MyErrorHandler implements org.xml.sax.ErrorHandler {
|
||||
|
||||
private IValidationContext<?> myContext;
|
||||
|
||||
MyErrorHandler(IValidationContext<?> theContext) {
|
||||
myContext = theContext;
|
||||
}
|
||||
|
||||
private void addIssue(SAXParseException theException, ResultSeverityEnum theSeverity) {
|
||||
SingleValidationMessage message = new SingleValidationMessage();
|
||||
message.setLocationLine(theException.getLineNumber());
|
||||
message.setLocationCol(theException.getColumnNumber());
|
||||
message.setMessage(theException.getLocalizedMessage());
|
||||
message.setSeverity(theSeverity);
|
||||
myContext.addValidationMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.FATAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(SAXParseException theException) {
|
||||
addIssue(theException, ResultSeverityEnum.WARNING);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -120,7 +121,19 @@ public class ValidationResult {
|
|||
location = null;
|
||||
}
|
||||
String severity = next.getSeverity() != null ? next.getSeverity().getCode() : null;
|
||||
OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, severity, next.getMessage(), location, Constants.OO_INFOSTATUS_PROCESSING);
|
||||
IBase issue = OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, severity, next.getMessage(), location, Constants.OO_INFOSTATUS_PROCESSING);
|
||||
|
||||
if (next.getLocationLine() != null || next.getLocationCol() != null) {
|
||||
String line = "(unknown)";
|
||||
if (next.getLocationLine() != null) {
|
||||
line = next.getLocationLine().toString();
|
||||
}
|
||||
String col = "(unknown)";
|
||||
if (next.getLocationCol() != null) {
|
||||
col = next.getLocationCol().toString();
|
||||
}
|
||||
OperationOutcomeUtil.addLocationToIssue(myCtx, issue, "Line " + line + ", Col " + col);
|
||||
}
|
||||
}
|
||||
|
||||
if (myMessages.isEmpty()) {
|
||||
|
|
|
@ -50,14 +50,12 @@ public interface IAnyResource extends IBaseResource {
|
|||
|
||||
String getId();
|
||||
|
||||
@Override
|
||||
IIdType getIdElement();
|
||||
|
||||
IPrimitiveType<String> getLanguageElement();
|
||||
|
||||
Object getUserData(String name);
|
||||
|
||||
@Override
|
||||
IAnyResource setId(String theId);
|
||||
|
||||
void setUserData(String name, Object value);
|
||||
|
|
|
@ -25,6 +25,8 @@ public interface IBaseBinary extends IBaseResource {
|
|||
|
||||
byte[] getContent();
|
||||
|
||||
IPrimitiveType<byte[]> getContentElement();
|
||||
|
||||
String getContentAsBase64();
|
||||
|
||||
String getContentType();
|
||||
|
|
|
@ -66,7 +66,7 @@ ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceVersionConstraintFail
|
|||
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceIndexedCompositeStringUniqueConstraintFailure=The operation has failed with a unique index constraint failure. This probably means that the operation was trying to create/update a resource that would have resulted in a duplicate value for a unique index.
|
||||
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.forcedIdConstraintFailure=The operation has failed with a client-assigned ID constraint failure. This typically means that multiple client threads are trying to create a new resource with the same client-assigned ID at the same time, and this thread was chosen to be rejected.
|
||||
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.externalizedBinaryStorageExtensionFoundInRequestBody=Illegal extension found in request payload: {0}
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.externalizedBinaryStorageExtensionFoundInRequestBody=Illegal extension found in request payload - URL "{0}" and value "{1}"
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlInvalidResourceType=Invalid match URL "{0}" - Unknown resource type: "{1}"
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlNoMatches=Invalid match URL "{0}" - No resources match this search
|
||||
|
@ -125,9 +125,9 @@ ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownPath=Unable to find content
|
|||
ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownType=Content in resource of type {0} at path {1} is not appropriate for binary storage: {2}
|
||||
|
||||
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
|
||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}
|
||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
|
||||
|
||||
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package ca.uhn.fhir.model.api;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ResourceMetadataKeyEnumTest {
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
assertEquals(-60968467, ResourceMetadataKeyEnum.PUBLISHED.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertNotEquals(ResourceMetadataKeyEnum.PROFILES, null);
|
||||
assertNotEquals(ResourceMetadataKeyEnum.PROFILES, "");
|
||||
assertNotEquals(ResourceMetadataKeyEnum.PROFILES, ResourceMetadataKeyEnum.PUBLISHED);
|
||||
assertEquals(ResourceMetadataKeyEnum.PROFILES, ResourceMetadataKeyEnum.PROFILES);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExtensionResourceEquals() {
|
||||
assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://bar"));
|
||||
assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), null);
|
||||
assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), "");
|
||||
assertEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"));
|
||||
|
||||
ResourceMetadataKeyEnum.ExtensionResourceMetadataKey foo = new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo");
|
||||
assertEquals(foo, foo);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package ca.uhn.fhir.model.api;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class TagTest {
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
Tag tag1 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
|
||||
Tag tag2 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
|
||||
Tag tag3 = new Tag().setScheme("scheme2").setTerm("term").setLabel("label");
|
||||
Tag tag4 = new Tag().setScheme("scheme").setTerm("term2").setLabel("label");
|
||||
|
||||
assertEquals(tag1, tag1);
|
||||
assertEquals(tag1, tag2);
|
||||
assertNotEquals(tag1, tag3);
|
||||
assertNotEquals(tag1, tag4);
|
||||
assertNotEquals(tag1, null);
|
||||
assertNotEquals(tag1, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
Tag tag1 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
|
||||
assertEquals(1920714536, tag1.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructors() throws URISyntaxException {
|
||||
assertTrue(new Tag().isEmpty());
|
||||
assertFalse(new Tag("http://foo").isEmpty());
|
||||
assertFalse(new Tag("http://foo", "http://bar").isEmpty());
|
||||
assertFalse(new Tag(new URI("http://foo"), new URI("http://bar"), "Label").isEmpty());
|
||||
assertTrue(new Tag((URI)null, null, "Label").isEmpty());
|
||||
|
||||
assertEquals("http://foo", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getSystem());
|
||||
assertEquals("http://bar", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getCode());
|
||||
assertEquals("Label", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getDisplay());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package ca.uhn.fhir.rest.api;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ConstantsTest {
|
||||
|
||||
@Test
|
||||
public void testConstants() {
|
||||
new Constants();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package ca.uhn.fhir.rest.api.server;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SimplePreResourceShowDetailsTest {
|
||||
|
||||
@Mock
|
||||
private IBaseResource myResource1;
|
||||
@Mock
|
||||
private IBaseResource myResource2;
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testSetResource_TooLow() {
|
||||
SimplePreResourceShowDetails details = new SimplePreResourceShowDetails(myResource1);
|
||||
details.setResource(-1, myResource2);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testSetResource_TooHigh() {
|
||||
SimplePreResourceShowDetails details = new SimplePreResourceShowDetails(myResource1);
|
||||
details.setResource(2, myResource2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetResource() {
|
||||
SimplePreResourceShowDetails details = new SimplePreResourceShowDetails(myResource1);
|
||||
details.setResource(0, myResource2);
|
||||
assertSame(myResource2, details.iterator().next());
|
||||
}
|
||||
}
|
|
@ -2,13 +2,34 @@ package ca.uhn.fhir.rest.param;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class TokenParamTest {
|
||||
@Test
|
||||
public void testEquals() {
|
||||
TokenParam tokenParam1 = new TokenParam("foo", "bar");
|
||||
TokenParam tokenParam2 = new TokenParam("foo", "bar");
|
||||
TokenParam tokenParam3 = new TokenParam("foo", "baz");
|
||||
assertEquals(tokenParam1, tokenParam1);
|
||||
assertEquals(tokenParam1, tokenParam2);
|
||||
assertNotEquals(tokenParam1, tokenParam3);
|
||||
assertNotEquals(tokenParam1, null);
|
||||
assertNotEquals(tokenParam1, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
TokenParam tokenParam1 = new TokenParam("foo", "bar");
|
||||
assertEquals(4716638, tokenParam1.hashCode());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIsEmpty() {
|
||||
assertFalse(new TokenParam("foo", "bar").isEmpty());
|
||||
assertTrue(new TokenParam("", "").isEmpty());
|
||||
assertTrue(new TokenParam().isEmpty());
|
||||
assertEquals("", new TokenParam().getValueNotNull());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
public class AsyncUtilTest {
|
||||
|
||||
@Test
|
||||
public void testSleep() {
|
||||
AsyncUtil.sleep(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSleepWithInterrupt() {
|
||||
AtomicBoolean outcomeHolder = new AtomicBoolean(true);
|
||||
Thread thread = new Thread(() -> {
|
||||
boolean outcome = AsyncUtil.sleep(10000);
|
||||
outcomeHolder.set(outcome);
|
||||
});
|
||||
thread.start();
|
||||
thread.interrupt();
|
||||
await().until(()-> outcomeHolder.get() == false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAwaitLatchAndThrowInternalErrorException() {
|
||||
AtomicBoolean outcomeHolder = new AtomicBoolean(false);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
AsyncUtil.awaitLatchAndThrowInternalErrorExceptionOnInterrupt(latch, 10, TimeUnit.SECONDS);
|
||||
} catch (InternalErrorException e) {
|
||||
outcomeHolder.set(true);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
thread.interrupt();
|
||||
await().until(()-> outcomeHolder.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAwaitLatchIgnoreInterruption() {
|
||||
AtomicBoolean outcomeHolder = new AtomicBoolean(true);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Thread thread = new Thread(() -> {
|
||||
boolean outcome = AsyncUtil.awaitLatchAndIgnoreInterrupt(latch, 10, TimeUnit.SECONDS);
|
||||
outcomeHolder.set(outcome);
|
||||
});
|
||||
thread.start();
|
||||
thread.interrupt();
|
||||
await().until(()-> outcomeHolder.get() == false);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class FileUtilTest {
|
||||
|
||||
@Test
|
||||
public void formatFileSize() {
|
||||
assertEquals("0 Bytes", FileUtil.formatFileSize(0).replace(",", "."));
|
||||
assertEquals("1 Bytes", FileUtil.formatFileSize(1).replace(",", "."));
|
||||
assertEquals("1.2 kB", FileUtil.formatFileSize(1234).replace(",", "."));
|
||||
assertEquals("12.1 kB", FileUtil.formatFileSize(12345).replace(",", "."));
|
||||
assertEquals("11.8 MB", FileUtil.formatFileSize(12345678).replace(",", "."));
|
||||
assertEquals("103.5 GB", FileUtil.formatFileSize(111111111111L).replace(",", "."));
|
||||
assertEquals("101.1 TB", FileUtil.formatFileSize(111111111111111L).replace(",", "."));
|
||||
assertEquals("10105.5 TB", FileUtil.formatFileSize(11111111111111111L).replace(",", "."));
|
||||
}
|
||||
}
|
|
@ -128,6 +128,56 @@ public class StopWatchTest {
|
|||
assertEquals("TASK1: 500ms\nTASK2: 100ms", taskDurations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatTaskDurationsDelayBetweenTasks() {
|
||||
StopWatch sw = new StopWatch();
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1000L);
|
||||
sw.startTask("TASK1");
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1500L);
|
||||
sw.endCurrentTask();
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(2000L);
|
||||
sw.startTask("TASK2");
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(2100L);
|
||||
sw.endCurrentTask();
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(2200L);
|
||||
String taskDurations = sw.formatTaskDurations();
|
||||
ourLog.info(taskDurations);
|
||||
assertEquals("TASK1: 500ms\n" +
|
||||
"Between: 500ms\n" +
|
||||
"TASK2: 100ms\n" +
|
||||
"After last task: 100ms", taskDurations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatTaskDurationsLongDelayBeforeStart() {
|
||||
StopWatch sw = new StopWatch(0);
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1000L);
|
||||
sw.startTask("TASK1");
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1500L);
|
||||
sw.startTask("TASK2");
|
||||
|
||||
StopWatch.setNowForUnitTestForUnitTest(1600L);
|
||||
String taskDurations = sw.formatTaskDurations();
|
||||
ourLog.info(taskDurations);
|
||||
assertEquals("Before first task: 1000ms\nTASK1: 500ms\nTASK2: 100ms", taskDurations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatTaskDurationsNoTasks() {
|
||||
StopWatch sw = new StopWatch(0);
|
||||
|
||||
String taskDurations = sw.formatTaskDurations();
|
||||
ourLog.info(taskDurations);
|
||||
assertEquals("No tasks", taskDurations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatThroughput60Ops4Min() {
|
||||
StopWatch sw = new StopWatch(DateUtils.addMinutes(new Date(), -4));
|
||||
|
@ -210,4 +260,34 @@ public class StopWatchTest {
|
|||
assertThat(string, matchesPattern("^[0-9]{3,4}ms$"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAppendRightAlignedNumber() {
|
||||
StringBuilder b= new StringBuilder();
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 0, 100);
|
||||
assertEquals("PFX100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 1, 100);
|
||||
assertEquals("PFX100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 2, 100);
|
||||
assertEquals("PFX100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 3, 100);
|
||||
assertEquals("PFX100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 4, 100);
|
||||
assertEquals("PFX0100", b.toString());
|
||||
|
||||
b.setLength(0);
|
||||
StopWatch.appendRightAlignedNumber(b, "PFX", 10, 100);
|
||||
assertEquals("PFX0000000100", b.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -247,7 +247,14 @@
|
|||
<groupId>org.fusesource.jansi</groupId>
|
||||
<artifactId>jansi</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Test Deps -->
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -398,7 +398,7 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
|
|||
}
|
||||
|
||||
protected IGenericClient newClientWithBaseUrl(CommandLine theCommandLine, String theBaseUrl, String theBasicAuthOptionName, String theBearerTokenOptionName) throws ParseException {
|
||||
myFhirCtx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000);
|
||||
myFhirCtx.getRestfulClientFactory().setSocketTimeout((int) DateUtils.MILLIS_PER_HOUR);
|
||||
IGenericClient retVal = myFhirCtx.newRestfulGenericClient(theBaseUrl);
|
||||
|
||||
String basicAuthHeaderValue = getAndParseOptionBasicAuthHeader(theCommandLine, theBasicAuthOptionName);
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.hl7.fhir.instance.model.api.IBaseBundle;
|
|||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
@ -79,7 +80,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
private Bundle getBundleFromFileDstu2(Integer limit, File inputFile, FhirContext ctx) throws IOException, UnsupportedEncodingException {
|
||||
private Bundle getBundleFromFileDstu2(Integer limit, File inputFile, FhirContext ctx) throws IOException {
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
|
@ -98,13 +99,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
break;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
int len;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
bos.write(buffer, 0, len);
|
||||
}
|
||||
byte[] exampleBytes = bos.toByteArray();
|
||||
String exampleString = new String(exampleBytes, "UTF-8");
|
||||
String exampleString = new String(exampleBytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (ourLog.isTraceEnabled()) {
|
||||
ourLog.trace("Next example: " + exampleString);
|
||||
|
@ -145,7 +146,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private org.hl7.fhir.dstu3.model.Bundle getBundleFromFileDstu3(Integer limit, File inputFile, FhirContext ctx) throws IOException, UnsupportedEncodingException {
|
||||
private org.hl7.fhir.dstu3.model.Bundle getBundleFromFileDstu3(Integer limit, File inputFile, FhirContext ctx) throws IOException {
|
||||
|
||||
org.hl7.fhir.dstu3.model.Bundle bundle = new org.hl7.fhir.dstu3.model.Bundle();
|
||||
bundle.setType(BundleType.TRANSACTION);
|
||||
|
@ -168,13 +169,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
break;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
int len;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
bos.write(buffer, 0, len);
|
||||
}
|
||||
byte[] exampleBytes = bos.toByteArray();
|
||||
String exampleString = new String(exampleBytes, "UTF-8");
|
||||
String exampleString = new String(exampleBytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (ourLog.isTraceEnabled()) {
|
||||
ourLog.trace("Next example: " + exampleString);
|
||||
|
@ -229,7 +230,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private org.hl7.fhir.r4.model.Bundle getBundleFromFileR4(Integer limit, File inputFile, FhirContext ctx) throws IOException, UnsupportedEncodingException {
|
||||
private org.hl7.fhir.r4.model.Bundle getBundleFromFileR4(Integer limit, File inputFile, FhirContext ctx) throws IOException {
|
||||
|
||||
org.hl7.fhir.r4.model.Bundle bundle = new org.hl7.fhir.r4.model.Bundle();
|
||||
bundle.setType(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION);
|
||||
|
@ -252,13 +253,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
break;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
int len;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
bos.write(buffer, 0, len);
|
||||
}
|
||||
byte[] exampleBytes = bos.toByteArray();
|
||||
String exampleString = new String(exampleBytes, "UTF-8");
|
||||
String exampleString = new String(exampleBytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (ourLog.isTraceEnabled()) {
|
||||
ourLog.trace("Next example: " + exampleString);
|
||||
|
@ -369,8 +370,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
|
||||
private void processBundleDstu2(FhirContext ctx, Bundle bundle) {
|
||||
|
||||
Map<String, Integer> ids = new HashMap<String, Integer>();
|
||||
Set<String> fullIds = new HashSet<String>();
|
||||
Set<String> fullIds = new HashSet<>();
|
||||
|
||||
for (Iterator<Entry> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
Entry next = iterator.next();
|
||||
|
@ -397,13 +397,14 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
Set<String> qualIds = new TreeSet<String>();
|
||||
for (Iterator<Entry> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
Entry next = iterator.next();
|
||||
Set<String> qualIds = new TreeSet<>();
|
||||
|
||||
for (Entry next : bundle.getEntry()) {
|
||||
if (next.getResource().getId().getIdPart() != null) {
|
||||
String nextId = next.getResource().getId().getValue();
|
||||
next.getRequest().setMethod(HTTPVerbEnum.PUT);
|
||||
next.getRequest().setUrl(nextId);
|
||||
qualIds.add(nextId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,15 +450,14 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
|
||||
private void processBundleDstu3(FhirContext ctx, org.hl7.fhir.dstu3.model.Bundle bundle) {
|
||||
|
||||
Map<String, Integer> ids = new HashMap<String, Integer>();
|
||||
Set<String> fullIds = new HashSet<String>();
|
||||
Set<String> fullIds = new HashSet<>();
|
||||
|
||||
for (Iterator<BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
BundleEntryComponent next = iterator.next();
|
||||
|
||||
// DataElement have giant IDs that seem invalid, need to investigate this..
|
||||
if ("Subscription".equals(next.getResource().getResourceType()) || "DataElement".equals(next.getResource().getResourceType())
|
||||
|| "OperationOutcome".equals(next.getResource().getResourceType()) || "OperationDefinition".equals(next.getResource().getResourceType())) {
|
||||
if ("Subscription".equals(next.getResource().getResourceType().name()) || "DataElement".equals(next.getResource().getResourceType().name())
|
||||
|| "OperationOutcome".equals(next.getResource().getResourceType().name()) || "OperationDefinition".equals(next.getResource().getResourceType().name())) {
|
||||
ourLog.info("Skipping " + next.getResource().getResourceType() + " example");
|
||||
iterator.remove();
|
||||
} else {
|
||||
|
@ -477,13 +477,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
Set<String> qualIds = new TreeSet<String>();
|
||||
for (Iterator<BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
BundleEntryComponent next = iterator.next();
|
||||
Set<String> qualIds = new TreeSet<>();
|
||||
for (BundleEntryComponent next : bundle.getEntry()) {
|
||||
if (next.getResource().getIdElement().getIdPart() != null) {
|
||||
String nextId = next.getResource().getIdElement().getValue();
|
||||
next.getRequest().setMethod(HTTPVerb.PUT);
|
||||
next.getRequest().setUrl(nextId);
|
||||
qualIds.add(nextId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -529,15 +529,14 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
|
||||
private void processBundleR4(FhirContext ctx, org.hl7.fhir.r4.model.Bundle bundle) {
|
||||
|
||||
Map<String, Integer> ids = new HashMap<String, Integer>();
|
||||
Set<String> fullIds = new HashSet<String>();
|
||||
Set<String> fullIds = new HashSet<>();
|
||||
|
||||
for (Iterator<org.hl7.fhir.r4.model.Bundle.BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next = iterator.next();
|
||||
|
||||
// DataElement have giant IDs that seem invalid, need to investigate this..
|
||||
if ("Subscription".equals(next.getResource().getResourceType()) || "DataElement".equals(next.getResource().getResourceType())
|
||||
|| "OperationOutcome".equals(next.getResource().getResourceType()) || "OperationDefinition".equals(next.getResource().getResourceType())) {
|
||||
if ("Subscription".equals(next.getResource().getResourceType().name()) || "DataElement".equals(next.getResource().getResourceType().name())
|
||||
|| "OperationOutcome".equals(next.getResource().getResourceType().name()) || "OperationDefinition".equals(next.getResource().getResourceType().name())) {
|
||||
ourLog.info("Skipping " + next.getResource().getResourceType() + " example");
|
||||
iterator.remove();
|
||||
} else {
|
||||
|
@ -557,13 +556,13 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
Set<String> qualIds = new TreeSet<String>();
|
||||
for (Iterator<org.hl7.fhir.r4.model.Bundle.BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
|
||||
org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next = iterator.next();
|
||||
Set<String> qualIds = new TreeSet<>();
|
||||
for (org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next : bundle.getEntry()) {
|
||||
if (next.getResource().getIdElement().getIdPart() != null) {
|
||||
String nextId = next.getResource().getIdElement().getValue();
|
||||
next.getRequest().setMethod(org.hl7.fhir.r4.model.Bundle.HTTPVerb.PUT);
|
||||
next.getRequest().setUrl(nextId);
|
||||
qualIds.add(nextId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,7 +634,7 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
|
||||
boolean cacheFile = theCommandLine.hasOption('c');
|
||||
|
||||
Collection<File> inputFiles = null;
|
||||
Collection<File> inputFiles;
|
||||
try {
|
||||
inputFiles = loadFile(specUrl, filepath, cacheFile);
|
||||
for (File inputFile : inputFiles) {
|
||||
|
@ -694,13 +693,11 @@ public class ExampleDataUploader extends BaseCommand {
|
|||
continue;
|
||||
}
|
||||
|
||||
boolean found = false;
|
||||
for (int j = 0; j < resources.size(); j++) {
|
||||
String candidateTarget = resources.get(j).getIdElement().getValue();
|
||||
if (isNotBlank(nextTarget) && nextTarget.equals(candidateTarget)) {
|
||||
ourLog.info("Reflexively adding resource {} to bundle as it is a reference target", nextTarget);
|
||||
subResourceList.add(resources.remove(j));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,35 +123,34 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept
|
|||
private void convertConceptMapToCsv(ConceptMap theConceptMap) {
|
||||
Path path = Paths.get(file);
|
||||
ourLog.info("Exporting ConceptMap to CSV: {}", path);
|
||||
try (
|
||||
Writer writer = Files.newBufferedWriter(path);
|
||||
CSVPrinter csvPrinter = new CSVPrinter(
|
||||
writer,
|
||||
CSVFormat
|
||||
.DEFAULT
|
||||
.withRecordSeparator("\n")
|
||||
.withHeader(Header.class)
|
||||
.withQuoteMode(QuoteMode.ALL));
|
||||
) {
|
||||
for (ConceptMapGroupComponent group : theConceptMap.getGroup()) {
|
||||
for (SourceElementComponent element : group.getElement()) {
|
||||
for (ConceptMap.TargetElementComponent target : element.getTarget()) {
|
||||
try (Writer writer = Files.newBufferedWriter(path)) {
|
||||
|
||||
List<String> columns = new ArrayList<>();
|
||||
columns.add(defaultString(group.getSource()));
|
||||
columns.add(defaultString(group.getSourceVersion()));
|
||||
columns.add(defaultString(group.getTarget()));
|
||||
columns.add(defaultString(group.getTargetVersion()));
|
||||
columns.add(defaultString(element.getCode()));
|
||||
columns.add(defaultString(element.getDisplay()));
|
||||
columns.add(defaultString(target.getCode()));
|
||||
columns.add(defaultString(target.getDisplay()));
|
||||
columns.add(defaultString(target.getEquivalence().toCode()));
|
||||
columns.add(defaultString(target.getComment()));
|
||||
CSVFormat format = CSVFormat.DEFAULT
|
||||
.withRecordSeparator("\n")
|
||||
.withHeader(Header.class)
|
||||
.withQuoteMode(QuoteMode.ALL);
|
||||
try (CSVPrinter csvPrinter = new CSVPrinter(writer, format)) {
|
||||
for (ConceptMapGroupComponent group : theConceptMap.getGroup()) {
|
||||
for (SourceElementComponent element : group.getElement()) {
|
||||
for (ConceptMap.TargetElementComponent target : element.getTarget()) {
|
||||
|
||||
csvPrinter.printRecord(columns);
|
||||
List<String> columns = new ArrayList<>();
|
||||
columns.add(defaultString(group.getSource()));
|
||||
columns.add(defaultString(group.getSourceVersion()));
|
||||
columns.add(defaultString(group.getTarget()));
|
||||
columns.add(defaultString(group.getTargetVersion()));
|
||||
columns.add(defaultString(element.getCode()));
|
||||
columns.add(defaultString(element.getDisplay()));
|
||||
columns.add(defaultString(target.getCode()));
|
||||
columns.add(defaultString(target.getDisplay()));
|
||||
columns.add(defaultString(target.getEquivalence().toCode()));
|
||||
columns.add(defaultString(target.getComment()));
|
||||
|
||||
csvPrinter.printRecord(columns);
|
||||
}
|
||||
}
|
||||
}
|
||||
csvPrinter.flush();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new InternalErrorException(ioe);
|
||||
|
|
|
@ -91,7 +91,7 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ package ca.uhn.fhir.cli;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
|
@ -44,7 +44,7 @@ public class ToggleSearchParametersCommand extends BaseCommand {
|
|||
Options options = new Options();
|
||||
addFhirVersionOption(options);
|
||||
addBaseUrlOption(options);
|
||||
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.SCT_URI + ")");
|
||||
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + ITermLoaderSvc.SCT_URI + ")");
|
||||
addBasicAuthOption(options);
|
||||
return options;
|
||||
}
|
||||
|
|
|
@ -20,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
|
||||
.operation()
|
||||
.onType(myFhirCtx.getResourceDefinition("CodeSystem").getImplementingClass())
|
||||
.named(UPLOAD_EXTERNAL_CODE_SYSTEM)
|
||||
.withParameters(inputParameters)
|
||||
.execute();
|
||||
if (ourLog.isDebugEnabled() || "true".equals(System.getProperty("test"))) {
|
||||
ourLog.info("Submitting parameters: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theInputParameters));
|
||||
}
|
||||
|
||||
IBaseParameters response;
|
||||
try {
|
||||
response = theClient
|
||||
.operation()
|
||||
.onType(myFhirCtx.getResourceDefinition("CodeSystem").getImplementingClass())
|
||||
.named(theOperationName)
|
||||
.withParameters(theInputParameters)
|
||||
.execute();
|
||||
} catch (BaseServerResponseException e) {
|
||||
if (e.getOperationOutcome() != null) {
|
||||
ourLog.error("Received the following response:\n{}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
ourLog.info("Upload complete!");
|
||||
ourLog.info("Response:\n{}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response));
|
||||
}
|
||||
|
||||
private void addFileToRequestBundle(IBaseParameters theInputParameters, String theFileName, byte[] theBytes) {
|
||||
|
||||
byte[] bytes = theBytes;
|
||||
String fileName = theFileName;
|
||||
|
||||
if (bytes.length > ourTransferSizeLimit) {
|
||||
ourLog.info("File size is greater than {} - Going to use a local file reference instead of a direct HTTP transfer. Note that this will only work when executing this command on the same server as the FHIR server itself.", FileUtil.formatFileSize(ourTransferSizeLimit));
|
||||
|
||||
String suffix = fileName.substring(fileName.lastIndexOf("."));
|
||||
try {
|
||||
File tempFile = File.createTempFile("hapi-fhir-cli", suffix);
|
||||
tempFile.deleteOnExit();
|
||||
try (OutputStream fileOutputStream = new FileOutputStream(tempFile, false)) {
|
||||
fileOutputStream.write(bytes);
|
||||
bytes = null;
|
||||
fileName = "localfile:" + tempFile.getAbsolutePath();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new CommandFailureException(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,7 +34,13 @@
|
|||
<!--
|
||||
It's useful to have this log when uploading big terminologies
|
||||
-->
|
||||
<logger name="ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl" additivity="false" level="info">
|
||||
<logger name="ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl" additivity="false" level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
<logger name="ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl" additivity="false" level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
<logger name="ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl" additivity="false" level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.test.utilities.LoggingRule;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
|
@ -24,10 +25,9 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
|
||||
public class ExportConceptMapToCsvCommandDstu3Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandDstu3Test.class);
|
||||
private static final String CM_URL = "http://example.com/conceptmap";
|
||||
|
@ -36,7 +36,7 @@ public class ExportConceptMapToCsvCommandDstu3Test {
|
|||
private static final String CS_URL_1 = "http://example.com/codesystem/1";
|
||||
private static final String CS_URL_2 = "http://example.com/codesystem/2";
|
||||
private static final String CS_URL_3 = "http://example.com/codesystem/3";
|
||||
private static final String FILE = "./target/output.csv";
|
||||
private static final String FILE = "./target/output_dstu3.csv";
|
||||
|
||||
private static String ourBase;
|
||||
private static IGenericClient ourClient;
|
||||
|
@ -44,13 +44,47 @@ public class ExportConceptMapToCsvCommandDstu3Test {
|
|||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static String ourVersion = "dstu3";
|
||||
@Rule
|
||||
public LoggingRule myLoggingRule = new LoggingRule();
|
||||
|
||||
static {
|
||||
System.setProperty("test", "true");
|
||||
}
|
||||
|
||||
@Rule
|
||||
public LoggingRule myLoggingRule = new LoggingRule();
|
||||
|
||||
@Test
|
||||
public void testExportConceptMapToCsvCommand() throws IOException {
|
||||
ourLog.debug("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[]{"export-conceptmap-to-csv",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
||||
"-l"});
|
||||
await().until(() -> new File(FILE).exists());
|
||||
|
||||
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
|
||||
|
||||
ourLog.info("Going to read file: {}", FILE);
|
||||
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
|
||||
assertEquals(expected, result);
|
||||
|
||||
FileUtils.deleteQuietly(new File(FILE));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
JettyUtil.closeServer(ourServer);
|
||||
|
@ -72,7 +106,7 @@ public class ExportConceptMapToCsvCommandDstu3Test {
|
|||
ourServer.setHandler(servletHandler);
|
||||
|
||||
JettyUtil.startServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
|
||||
ourBase = "http://localhost:" + ourPort;
|
||||
|
||||
|
@ -81,38 +115,6 @@ public class ExportConceptMapToCsvCommandDstu3Test {
|
|||
ourClient.create().resource(createConceptMap()).execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportConceptMapToCsvCommand() throws IOException {
|
||||
ourLog.debug("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[] {"export-conceptmap-to-csv",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
||||
"-l"});
|
||||
|
||||
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
|
||||
|
||||
ourLog.info("Going to read file: {}", FILE);
|
||||
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
|
||||
assertEquals(expected, result);
|
||||
|
||||
FileUtils.deleteQuietly(new File(FILE));
|
||||
}
|
||||
|
||||
static ConceptMap createConceptMap() {
|
||||
ConceptMap conceptMap = new ConceptMap();
|
||||
conceptMap
|
||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
@ -22,10 +23,9 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
|
||||
public class ExportConceptMapToCsvCommandR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandR4Test.class);
|
||||
private static final String CM_URL = "http://example.com/conceptmap";
|
||||
|
@ -34,7 +34,7 @@ public class ExportConceptMapToCsvCommandR4Test {
|
|||
private static final String CS_URL_1 = "http://example.com/codesystem/1";
|
||||
private static final String CS_URL_2 = "http://example.com/codesystem/2";
|
||||
private static final String CS_URL_3 = "http://example.com/codesystem/3";
|
||||
private static final String FILE = new File("./target/output.csv").getAbsolutePath();
|
||||
private static final String FILE = new File("./target/output_r4.csv").getAbsolutePath();
|
||||
|
||||
private static String ourBase;
|
||||
private static IGenericClient ourClient;
|
||||
|
@ -47,6 +47,37 @@ public class ExportConceptMapToCsvCommandR4Test {
|
|||
System.setProperty("test", "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportConceptMapToCsvCommand() throws IOException {
|
||||
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[]{"export-conceptmap-to-csv",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
||||
"-l"});
|
||||
await().until(() -> new File(FILE).exists());
|
||||
|
||||
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
|
||||
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
|
||||
assertEquals(expected, result);
|
||||
|
||||
FileUtils.deleteQuietly(new File(FILE));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
JettyUtil.closeServer(ourServer);
|
||||
|
@ -68,7 +99,7 @@ public class ExportConceptMapToCsvCommandR4Test {
|
|||
ourServer.setHandler(servletHandler);
|
||||
|
||||
JettyUtil.startServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
|
||||
ourBase = "http://localhost:" + ourPort;
|
||||
|
||||
|
@ -77,36 +108,6 @@ public class ExportConceptMapToCsvCommandR4Test {
|
|||
ourClient.create().resource(createConceptMap()).execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportConceptMapToCsvCommand() throws IOException {
|
||||
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
|
||||
|
||||
App.main(new String[] {"export-conceptmap-to-csv",
|
||||
"-v", ourVersion,
|
||||
"-t", ourBase,
|
||||
"-u", CM_URL,
|
||||
"-f", FILE,
|
||||
"-l"});
|
||||
|
||||
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
|
||||
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
|
||||
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
|
||||
assertEquals(expected, result);
|
||||
|
||||
FileUtils.deleteQuietly(new File(FILE));
|
||||
}
|
||||
|
||||
static ConceptMap createConceptMap() {
|
||||
ConceptMap conceptMap = new ConceptMap();
|
||||
conceptMap
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -51,23 +51,19 @@ public class OkHttpRestfulResponse extends BaseHttpResponse implements IHttpResp
|
|||
this.myResponse = theResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntitity() throws IOException {
|
||||
bufferEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntity() throws IOException {
|
||||
if (myEntityBuffered) {
|
||||
return;
|
||||
}
|
||||
InputStream responseEntity = readEntity();
|
||||
if (responseEntity != null) {
|
||||
myEntityBuffered = true;
|
||||
try {
|
||||
myEntityBytes = IOUtils.toByteArray(responseEntity);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new InternalErrorException(e);
|
||||
try (InputStream responseEntity = readEntity()) {
|
||||
if (responseEntity != null) {
|
||||
myEntityBuffered = true;
|
||||
try {
|
||||
myEntityBytes = IOUtils.toByteArray(responseEntity);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.client.apache;
|
|||
*/
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.rest.client.impl.BaseHttpResponse;
|
||||
|
@ -53,25 +54,19 @@ public class ApacheHttpResponse extends BaseHttpResponse implements IHttpRespons
|
|||
this.myResponse = theResponse;
|
||||
}
|
||||
|
||||
@Deprecated // override deprecated method
|
||||
@Override
|
||||
public void bufferEntitity() throws IOException {
|
||||
bufferEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntity() throws IOException {
|
||||
if (myEntityBuffered) {
|
||||
return;
|
||||
}
|
||||
InputStream respEntity = readEntity();
|
||||
if (respEntity != null) {
|
||||
this.myEntityBuffered = true;
|
||||
try {
|
||||
this.myEntityBytes = IOUtils.toByteArray(respEntity);
|
||||
} catch (IllegalStateException e) {
|
||||
// FIXME resouce leak
|
||||
throw new InternalErrorException(e);
|
||||
try (InputStream respEntity = readEntity()) {
|
||||
if (respEntity != null) {
|
||||
this.myEntityBuffered = true;
|
||||
try {
|
||||
this.myEntityBytes = IOUtils.toByteArray(respEntity);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +98,7 @@ public class ApacheHttpResponse extends BaseHttpResponse implements IHttpRespons
|
|||
if (Constants.STATUS_HTTP_204_NO_CONTENT != myResponse.getStatusLine().getStatusCode()) {
|
||||
ourLog.debug("Response did not specify a charset, defaulting to utf-8");
|
||||
}
|
||||
charset = Charset.forName("UTF-8");
|
||||
charset = StandardCharsets.UTF_8;
|
||||
}
|
||||
|
||||
return new InputStreamReader(readEntity(), charset);
|
||||
|
@ -115,11 +110,7 @@ public class ApacheHttpResponse extends BaseHttpResponse implements IHttpRespons
|
|||
if (myResponse.getAllHeaders() != null) {
|
||||
for (Header next : myResponse.getAllHeaders()) {
|
||||
String name = next.getName().toLowerCase();
|
||||
List<String> list = headers.get(name);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
headers.put(name, list);
|
||||
}
|
||||
List<String> list = headers.computeIfAbsent(name, k -> new ArrayList<>());
|
||||
list.add(next.getValue());
|
||||
}
|
||||
|
||||
|
|
|
@ -71,18 +71,18 @@ public class ApacheRestfulClientFactory extends RestfulClientFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ApacheHttpClient getHttpClient(String theServerBase) {
|
||||
protected synchronized ApacheHttpClient getHttpClient(String theServerBase) {
|
||||
return new ApacheHttpClient(getNativeHttpClient(), new StringBuilder(theServerBase), null, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams,
|
||||
public synchronized IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams,
|
||||
String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) {
|
||||
return new ApacheHttpClient(getNativeHttpClient(), theUrl, theIfNoneExistParams, theIfNoneExistString, theRequestType,
|
||||
theHeaders);
|
||||
}
|
||||
|
||||
public synchronized HttpClient getNativeHttpClient() {
|
||||
public HttpClient getNativeHttpClient() {
|
||||
if (myHttpClient == null) {
|
||||
|
||||
//FIXME potential resoource leak
|
||||
|
|
|
@ -71,52 +71,52 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getConnectionRequestTimeout() {
|
||||
public synchronized int getConnectionRequestTimeout() {
|
||||
return myConnectionRequestTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConnectTimeout() {
|
||||
public synchronized int getConnectTimeout() {
|
||||
return myConnectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the proxy username to authenticate with the HTTP proxy
|
||||
*/
|
||||
protected String getProxyUsername() {
|
||||
protected synchronized String getProxyUsername() {
|
||||
return myProxyUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the proxy password to authenticate with the HTTP proxy
|
||||
*/
|
||||
protected String getProxyPassword() {
|
||||
protected synchronized String getProxyPassword() {
|
||||
return myProxyPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProxyCredentials(String theUsername, String thePassword) {
|
||||
public synchronized void setProxyCredentials(String theUsername, String thePassword) {
|
||||
myProxyUsername = theUsername;
|
||||
myProxyPassword = thePassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerValidationModeEnum getServerValidationMode() {
|
||||
public synchronized ServerValidationModeEnum getServerValidationMode() {
|
||||
return myServerValidationMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSocketTimeout() {
|
||||
public synchronized int getSocketTimeout() {
|
||||
return mySocketTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPoolMaxTotal() {
|
||||
public synchronized int getPoolMaxTotal() {
|
||||
return myPoolMaxTotal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPoolMaxPerRoute() {
|
||||
public synchronized int getPoolMaxPerRoute() {
|
||||
return myPoolMaxPerRoute;
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setServerValidationMode(ServerValidationModeEnum theServerValidationMode) {
|
||||
public synchronized void setServerValidationMode(ServerValidationModeEnum theServerValidationMode) {
|
||||
Validate.notNull(theServerValidationMode, "theServerValidationMode may not be null");
|
||||
myServerValidationMode = theServerValidationMode;
|
||||
}
|
||||
|
@ -242,13 +242,13 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
|||
|
||||
@Deprecated // override deprecated method
|
||||
@Override
|
||||
public ServerValidationModeEnum getServerValidationModeEnum() {
|
||||
public synchronized ServerValidationModeEnum getServerValidationModeEnum() {
|
||||
return getServerValidationMode();
|
||||
}
|
||||
|
||||
@Deprecated // override deprecated method
|
||||
@Override
|
||||
public void setServerValidationModeEnum(ServerValidationModeEnum theServerValidationMode) {
|
||||
public synchronized void setServerValidationModeEnum(ServerValidationModeEnum theServerValidationMode) {
|
||||
setServerValidationMode(theServerValidationMode);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.client.interceptor;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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.*;
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-converter</artifactId>
|
||||
<!-- TODO: BND changed to jar temporarily -->
|
||||
<packaging>jar</packaging>
|
||||
<packaging>bundle</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -152,10 +151,6 @@
|
|||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<!--
|
||||
TODO: BND
|
||||
With this enabled, JAR file contains a bunch of .class files that
|
||||
seem to get copied in from org.hl7.fhir.convertors
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
|
@ -170,7 +165,6 @@
|
|||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
-->
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
@ -29,6 +29,8 @@ import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
|||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||
import org.hl7.fhir.converter.NullVersionConverterAdvisor30;
|
||||
import org.hl7.fhir.converter.NullVersionConverterAdvisor40;
|
||||
import org.hl7.fhir.convertors.*;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -20,6 +20,7 @@ package org.hl7.fhir.convertors;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertorAdvisor30;
|
||||
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
import org.hl7.fhir.dstu3.model.ValueSet;
|
|
@ -1,4 +1,4 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -20,6 +20,7 @@ package org.hl7.fhir.convertors;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertorAdvisor40;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.dstu2.model.Resource;
|
||||
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
|
|
@ -1,4 +1,4 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -20,6 +20,7 @@ package org.hl7.fhir.convertors;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertorAdvisor50;
|
||||
import org.hl7.fhir.dstu2.model.Resource;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
|
@ -1,7 +1,8 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertor_10_30;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.dstu2.model.Resource;
|
|
@ -1,7 +1,8 @@
|
|||
package org.hl7.fhir.convertors;
|
||||
package org.hl7.fhir.converter;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertor_14_30;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.junit.Test;
|
||||
|
|
@ -87,6 +87,12 @@
|
|||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.28</version>
|
||||
</dependency>
|
||||
<!-- Needed for JEE/Servlet support -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -20,7 +20,7 @@ package ca.uhn.hapi.fhir.docs;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.convertors.NullVersionConverterAdvisor30;
|
||||
import org.hl7.fhir.converter.NullVersionConverterAdvisor30;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_10_30;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_14_30;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
|
|
@ -290,7 +290,7 @@ public class ValidatorExamples {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
|
||||
// TODO: implement (or return null if your implementation does not support this function)
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ public class IgPackValidationSupportDstu3 implements IValidationSupport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,11 +51,6 @@ public class JaxRsHttpResponse extends BaseHttpResponse implements IHttpResponse
|
|||
this.myResponse = theResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntitity() throws IOException {
|
||||
bufferEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEntity() throws IOException {
|
||||
if(!myBufferedEntity && myResponse.hasEntity()) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- Note: HAPI projects use the "hapi-fhir" POM as their base to provide easy management. You do not need to use this in your own projects, so the "parent" tag and it's contents below may be removed
|
||||
<!-- Note: HAPI projects use the "hapi-fhir" POM as their base to provide easy management. You do not need to use this in your own projects, so the "parent" tag and it's contents below may be removed
|
||||
if you are using this file as a basis for your own project. -->
|
||||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
|
@ -83,12 +83,12 @@
|
|||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-test-utilities</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-test-utilities</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
@ -120,5 +120,4 @@
|
|||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
|
@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PayloadTooLargeException;
|
||||
import com.google.common.hash.HashFunction;
|
||||
import com.google.common.hash.Hashing;
|
||||
|
@ -34,6 +33,8 @@ import javax.annotation.Nonnull;
|
|||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
||||
private final SecureRandom myRandom;
|
||||
private final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
@ -66,7 +67,8 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
myMinimumBinarySize = theMinimumBinarySize;
|
||||
}
|
||||
|
||||
String newRandomId() {
|
||||
@Override
|
||||
public String newBlobId() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (int i = 0; i < ID_LENGTH; i++) {
|
||||
int nextInt = Math.abs(myRandom.nextInt());
|
||||
|
@ -89,13 +91,13 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
|
||||
@Nonnull
|
||||
CountingInputStream createCountingInputStream(InputStream theInputStream) {
|
||||
InputStream is = ByteStreams.limit(theInputStream, myMaximumBinarySize + 1L);
|
||||
InputStream is = ByteStreams.limit(theInputStream, getMaximumBinarySize() + 1L);
|
||||
return new CountingInputStream(is) {
|
||||
@Override
|
||||
public int getCount() {
|
||||
int retVal = super.getCount();
|
||||
if (retVal > myMaximumBinarySize) {
|
||||
throw new PayloadTooLargeException("Binary size exceeds maximum: " + myMaximumBinarySize);
|
||||
if (retVal > getMaximumBinarySize()) {
|
||||
throw new PayloadTooLargeException("Binary size exceeds maximum: " + getMaximumBinarySize());
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
@ -103,4 +105,11 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
}
|
||||
|
||||
|
||||
String provideIdForNewBlob(String theBlobIdOrNull) {
|
||||
String id = theBlobIdOrNull;
|
||||
if (isBlank(theBlobIdOrNull)) {
|
||||
id = newBlobId();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,20 +86,13 @@ public class BinaryAccessProvider {
|
|||
|
||||
IBinaryTarget target = findAttachmentForRequest(resource, path, theRequestDetails);
|
||||
|
||||
Optional<? extends IBaseExtension<?, ?>> attachmentId = target
|
||||
.getTarget()
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
|
||||
.findFirst();
|
||||
|
||||
Optional<String> attachmentId = target.getAttachmentId();
|
||||
if (attachmentId.isPresent()) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
IPrimitiveType<String> value = (IPrimitiveType<String>) attachmentId.get().getValue();
|
||||
String blobId = value.getValueAsString();
|
||||
String blobId = attachmentId.get();
|
||||
|
||||
IBinaryStorageSvc.StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(theResourceId, blobId);
|
||||
StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(theResourceId, blobId);
|
||||
if (blobDetails == null) {
|
||||
String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "unknownBlobId");
|
||||
throw new InvalidRequestException(msg);
|
||||
|
@ -179,7 +172,7 @@ public class BinaryAccessProvider {
|
|||
if (size > 0) {
|
||||
if (myBinaryStorageSvc != null) {
|
||||
if (myBinaryStorageSvc.shouldStoreBlob(size, theResourceId, requestContentType)) {
|
||||
IBinaryStorageSvc.StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(theResourceId, requestContentType, theRequestDetails.getInputStream());
|
||||
StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(theResourceId, null, requestContentType, theRequestDetails.getInputStream());
|
||||
size = storedDetails.getBytes();
|
||||
blobId = storedDetails.getBlobId();
|
||||
Validate.notBlank(blobId, "BinaryStorageSvc returned a null blob ID"); // should not happen
|
||||
|
@ -192,19 +185,7 @@ public class BinaryAccessProvider {
|
|||
size = bytes.length;
|
||||
target.setData(bytes);
|
||||
} else {
|
||||
|
||||
target
|
||||
.getTarget()
|
||||
.getExtension()
|
||||
.removeIf(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()));
|
||||
target.setData(null);
|
||||
|
||||
IBaseExtension<?, ?> ext = target.getTarget().addExtension();
|
||||
ext.setUrl(JpaConstants.EXT_EXTERNALIZED_BINARY_ID);
|
||||
ext.setUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED, Boolean.TRUE);
|
||||
IPrimitiveType<String> blobIdString = (IPrimitiveType<String>) myCtx.getElementDefinition("string").newInstance();
|
||||
blobIdString.setValueAsString(blobId);
|
||||
ext.setValue(blobIdString);
|
||||
replaceDataWithExtension(target, blobId);
|
||||
}
|
||||
|
||||
target.setContentType(requestContentType);
|
||||
|
@ -217,52 +198,81 @@ public class BinaryAccessProvider {
|
|||
return outcome.getResource();
|
||||
}
|
||||
|
||||
public void replaceDataWithExtension(IBinaryTarget theTarget, String theBlobId) {
|
||||
theTarget
|
||||
.getTarget()
|
||||
.getExtension()
|
||||
.removeIf(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()));
|
||||
theTarget.setData(null);
|
||||
|
||||
IBaseExtension<?, ?> ext = theTarget.getTarget().addExtension();
|
||||
ext.setUrl(JpaConstants.EXT_EXTERNALIZED_BINARY_ID);
|
||||
ext.setUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED, Boolean.TRUE);
|
||||
IPrimitiveType<String> blobIdString = (IPrimitiveType<String>) myCtx.getElementDefinition("string").newInstance();
|
||||
blobIdString.setValueAsString(theBlobId);
|
||||
ext.setValue(blobIdString);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private IBinaryTarget findAttachmentForRequest(IBaseResource theResource, String thePath, ServletRequestDetails theRequestDetails) {
|
||||
FhirContext ctx = theRequestDetails.getFhirContext();
|
||||
|
||||
Optional<IBase> type = ctx.newFluentPath().evaluateFirst(theResource, thePath, IBase.class);
|
||||
String resType = myCtx.getResourceDefinition(theResource).getName();
|
||||
Optional<IBase> type = myCtx.newFluentPath().evaluateFirst(theResource, thePath, IBase.class);
|
||||
String resType = this.myCtx.getResourceDefinition(theResource).getName();
|
||||
if (!type.isPresent()) {
|
||||
String msg = myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownPath", resType, thePath);
|
||||
String msg = this.myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownPath", resType, thePath);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
IBase element = type.get();
|
||||
|
||||
Optional<IBinaryTarget> binaryTarget = toBinaryTarget(element);
|
||||
|
||||
if (binaryTarget.isPresent() == false) {
|
||||
BaseRuntimeElementDefinition<?> def2 = myCtx.getElementDefinition(element.getClass());
|
||||
String msg = this.myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownType", resType, thePath, def2.getName());
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
return binaryTarget.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Optional<IBinaryTarget> toBinaryTarget(IBase theElement) {
|
||||
IBinaryTarget binaryTarget = null;
|
||||
|
||||
// Path is attachment
|
||||
BaseRuntimeElementDefinition<?> def = ctx.getElementDefinition(type.get().getClass());
|
||||
BaseRuntimeElementDefinition<?> def = myCtx.getElementDefinition(theElement.getClass());
|
||||
if (def.getName().equals("Attachment")) {
|
||||
ICompositeType attachment = (ICompositeType) type.get();
|
||||
return new IBinaryTarget() {
|
||||
ICompositeType attachment = (ICompositeType) theElement;
|
||||
binaryTarget = new IBinaryTarget() {
|
||||
@Override
|
||||
public void setSize(Integer theSize) {
|
||||
AttachmentUtil.setSize(myCtx, attachment, theSize);
|
||||
AttachmentUtil.setSize(BinaryAccessProvider.this.myCtx, attachment, theSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return AttachmentUtil.getOrCreateContentType(myCtx, attachment).getValueAsString();
|
||||
return AttachmentUtil.getOrCreateContentType(BinaryAccessProvider.this.myCtx, attachment).getValueAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData() {
|
||||
IPrimitiveType<byte[]> dataDt = AttachmentUtil.getOrCreateData(theRequestDetails.getFhirContext(), attachment);
|
||||
IPrimitiveType<byte[]> dataDt = AttachmentUtil.getOrCreateData(myCtx, attachment);
|
||||
return dataDt.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBaseHasExtensions getTarget() {
|
||||
return (IBaseHasExtensions) AttachmentUtil.getOrCreateData(theRequestDetails.getFhirContext(), attachment);
|
||||
return (IBaseHasExtensions) AttachmentUtil.getOrCreateData(myCtx, attachment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentType(String theContentType) {
|
||||
AttachmentUtil.setContentType(myCtx, attachment, theContentType);
|
||||
AttachmentUtil.setContentType(BinaryAccessProvider.this.myCtx, attachment, theContentType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setData(byte[] theBytes) {
|
||||
AttachmentUtil.setData(theRequestDetails.getFhirContext(), attachment, theBytes);
|
||||
AttachmentUtil.setData(myCtx, attachment, theBytes);
|
||||
}
|
||||
|
||||
|
||||
|
@ -271,8 +281,8 @@ public class BinaryAccessProvider {
|
|||
|
||||
// Path is Binary
|
||||
if (def.getName().equals("Binary")) {
|
||||
IBaseBinary binary = (IBaseBinary) type.get();
|
||||
return new IBinaryTarget() {
|
||||
IBaseBinary binary = (IBaseBinary) theElement;
|
||||
binaryTarget = new IBinaryTarget() {
|
||||
@Override
|
||||
public void setSize(Integer theSize) {
|
||||
// ignore
|
||||
|
@ -290,7 +300,7 @@ public class BinaryAccessProvider {
|
|||
|
||||
@Override
|
||||
public IBaseHasExtensions getTarget() {
|
||||
return (IBaseHasExtensions) BinaryUtil.getOrCreateData(myCtx, binary);
|
||||
return (IBaseHasExtensions) BinaryUtil.getOrCreateData(BinaryAccessProvider.this.myCtx, binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -308,9 +318,7 @@ public class BinaryAccessProvider {
|
|||
};
|
||||
}
|
||||
|
||||
String msg = myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownType", resType, thePath, def.getName());
|
||||
throw new InvalidRequestException(msg);
|
||||
|
||||
return Optional.ofNullable(binaryTarget);
|
||||
}
|
||||
|
||||
private String validateResourceTypeAndPath(@IdParam IIdType theResourceId, @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType<String> thePath) {
|
||||
|
@ -340,25 +348,5 @@ public class BinaryAccessProvider {
|
|||
return dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an Attachment datatype or Binary resource, since they both
|
||||
* hold binary content but don't look entirely similar
|
||||
*/
|
||||
private interface IBinaryTarget {
|
||||
|
||||
void setSize(Integer theSize);
|
||||
|
||||
String getContentType();
|
||||
|
||||
void setContentType(String theContentType);
|
||||
|
||||
byte[] getData();
|
||||
|
||||
void setData(byte[] theBytes);
|
||||
|
||||
IBaseHasExtensions getTarget();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -20,23 +20,37 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.IModelVisitor2;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.jpa.model.util.JpaConstants.EXT_EXTERNALIZED_BINARY_ID;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
@Interceptor
|
||||
public class BinaryStorageInterceptor {
|
||||
|
||||
|
@ -45,12 +59,39 @@ public class BinaryStorageInterceptor {
|
|||
private IBinaryStorageSvc myBinaryStorageSvc;
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
@Autowired
|
||||
private BinaryAccessProvider myBinaryAccessProvider;
|
||||
private Class<? extends IPrimitiveType<byte[]>> myBinaryType;
|
||||
private String myDeferredListKey;
|
||||
private long myAutoDeExternalizeMaximumBytes = 10 * FileUtils.ONE_MB;
|
||||
|
||||
/**
|
||||
* Any externalized binaries will be rehydrated if their size is below this thhreshold when
|
||||
* reading the resource back. Default is 10MB.
|
||||
*/
|
||||
public long getAutoDeExternalizeMaximumBytes() {
|
||||
return myAutoDeExternalizeMaximumBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any externalized binaries will be rehydrated if their size is below this thhreshold when
|
||||
* reading the resource back. Default is 10MB.
|
||||
*/
|
||||
public void setAutoDeExternalizeMaximumBytes(long theAutoDeExternalizeMaximumBytes) {
|
||||
myAutoDeExternalizeMaximumBytes = theAutoDeExternalizeMaximumBytes;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
myBinaryType = (Class<? extends IPrimitiveType<byte[]>>) myCtx.getElementDefinition("base64Binary").getImplementingClass();
|
||||
myDeferredListKey = getClass().getName() + "_" + hashCode() + "_DEFERRED_LIST";
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE)
|
||||
public void expungeResource(AtomicInteger theCounter, IBaseResource theResource) {
|
||||
|
||||
Class<? extends IBase> binaryType = myCtx.getElementDefinition("base64Binary").getImplementingClass();
|
||||
List<? extends IBase> binaryElements = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, binaryType);
|
||||
List<? extends IBase> binaryElements = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, myBinaryType);
|
||||
|
||||
List<String> attachmentIds = binaryElements
|
||||
.stream()
|
||||
|
@ -67,4 +108,215 @@ public class BinaryStorageInterceptor {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
|
||||
public void extractLargeBinariesBeforeCreate(ServletRequestDetails theRequestDetails, IBaseResource theResource, Pointcut thePointcut) throws IOException {
|
||||
extractLargeBinaries(theRequestDetails, theResource, thePointcut);
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
|
||||
public void extractLargeBinariesBeforeUpdate(ServletRequestDetails theRequestDetails, IBaseResource thePreviousResource, IBaseResource theResource, Pointcut thePointcut) throws IOException {
|
||||
blockIllegalExternalBinaryIds(thePreviousResource, theResource);
|
||||
extractLargeBinaries(theRequestDetails, theResource, thePointcut);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't allow clients to submit resources with binary storage attachments declared unless the ID was already in the
|
||||
* resource. In other words, only HAPI itself may add a binary storage ID extension to a resource unless that
|
||||
* extension was already present.
|
||||
*/
|
||||
private void blockIllegalExternalBinaryIds(IBaseResource thePreviousResource, IBaseResource theResource) {
|
||||
Set<String> existingBinaryIds = new HashSet<>();
|
||||
if (thePreviousResource != null) {
|
||||
List<? extends IPrimitiveType<byte[]>> base64fields = myCtx.newTerser().getAllPopulatedChildElementsOfType(thePreviousResource, myBinaryType);
|
||||
for (IPrimitiveType<byte[]> nextBase64 : base64fields) {
|
||||
if (nextBase64 instanceof IBaseHasExtensions) {
|
||||
((IBaseHasExtensions) nextBase64)
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t -> t.getUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED) == null)
|
||||
.filter(t -> EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
|
||||
.map(t -> (IPrimitiveType) t.getValue())
|
||||
.map(t -> t.getValueAsString())
|
||||
.filter(t -> isNotBlank(t))
|
||||
.forEach(t -> existingBinaryIds.add(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<? extends IPrimitiveType<byte[]>> base64fields = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, myBinaryType);
|
||||
for (IPrimitiveType<byte[]> nextBase64 : base64fields) {
|
||||
if (nextBase64 instanceof IBaseHasExtensions) {
|
||||
Optional<String> hasExternalizedBinaryReference = ((IBaseHasExtensions) nextBase64)
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t -> t.getUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED) == null)
|
||||
.filter(t -> t.getUrl().equals(EXT_EXTERNALIZED_BINARY_ID))
|
||||
.map(t->(IPrimitiveType) t.getValue())
|
||||
.map(t->t.getValueAsString())
|
||||
.filter(t->isNotBlank(t))
|
||||
.filter(t->{
|
||||
return !existingBinaryIds.contains(t);
|
||||
}).findFirst();
|
||||
|
||||
if (hasExternalizedBinaryReference.isPresent()) {
|
||||
String msg = myCtx.getLocalizer().getMessage(BaseHapiFhirDao.class, "externalizedBinaryStorageExtensionFoundInRequestBody", EXT_EXTERNALIZED_BINARY_ID, hasExternalizedBinaryReference.get());
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void extractLargeBinaries(ServletRequestDetails theRequestDetails, IBaseResource theResource, Pointcut thePointcut) throws IOException {
|
||||
if (theRequestDetails == null) {
|
||||
// RequestDetails will only be null for internal HAPI events. If externalization is required for them it will need to be done in a different way.
|
||||
return;
|
||||
}
|
||||
IIdType resourceId = theResource.getIdElement();
|
||||
if (!resourceId.hasResourceType() && resourceId.hasIdPart()) {
|
||||
String resourceType = myCtx.getResourceDefinition(theResource).getName();
|
||||
resourceId = new IdType(resourceType + "/" + resourceId.getIdPart());
|
||||
}
|
||||
|
||||
List<IBinaryTarget> attachments = recursivelyScanResourceForBinaryData(theResource);
|
||||
for (IBinaryTarget nextTarget : attachments) {
|
||||
byte[] data = nextTarget.getData();
|
||||
if (data != null && data.length > 0) {
|
||||
|
||||
long nextPayloadLength = data.length;
|
||||
String nextContentType = nextTarget.getContentType();
|
||||
boolean shouldStoreBlob = myBinaryStorageSvc.shouldStoreBlob(nextPayloadLength, resourceId, nextContentType);
|
||||
if (shouldStoreBlob) {
|
||||
|
||||
String newBlobId;
|
||||
if (resourceId.hasIdPart()) {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
|
||||
StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(resourceId, null, nextContentType, inputStream);
|
||||
newBlobId = storedDetails.getBlobId();
|
||||
} else {
|
||||
assert thePointcut == Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED : thePointcut.name();
|
||||
newBlobId = myBinaryStorageSvc.newBlobId();
|
||||
List<DeferredBinaryTarget> deferredBinaryTargets = getOrCreateDeferredBinaryStorageMap(theRequestDetails);
|
||||
DeferredBinaryTarget newDeferredBinaryTarget = new DeferredBinaryTarget(newBlobId, nextTarget, data);
|
||||
deferredBinaryTargets.add(newDeferredBinaryTarget);
|
||||
}
|
||||
|
||||
myBinaryAccessProvider.replaceDataWithExtension(nextTarget, newBlobId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<DeferredBinaryTarget> getOrCreateDeferredBinaryStorageMap(ServletRequestDetails theRequestDetails) {
|
||||
List<DeferredBinaryTarget> deferredBinaryTargets = (List<DeferredBinaryTarget>) theRequestDetails.getUserData().get(getDeferredListKey());
|
||||
if (deferredBinaryTargets == null) {
|
||||
deferredBinaryTargets = new ArrayList<>();
|
||||
theRequestDetails.getUserData().put(getDeferredListKey(), deferredBinaryTargets);
|
||||
}
|
||||
return deferredBinaryTargets;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED)
|
||||
public void storeLargeBinariesBeforeCreatePersistence(ServletRequestDetails theRequestDetails, IBaseResource theResource, Pointcut thePoincut) throws IOException {
|
||||
if (theRequestDetails == null) {
|
||||
return;
|
||||
}
|
||||
List<DeferredBinaryTarget> deferredBinaryTargets = (List<DeferredBinaryTarget>) theRequestDetails.getUserData().get(getDeferredListKey());
|
||||
if (deferredBinaryTargets != null) {
|
||||
IIdType resourceId = theResource.getIdElement();
|
||||
for (DeferredBinaryTarget next : deferredBinaryTargets) {
|
||||
String blobId = next.getBlobId();
|
||||
IBinaryTarget target = next.getBinaryTarget();
|
||||
InputStream dataStream = next.getDataStream();
|
||||
String contentType = target.getContentType();
|
||||
myBinaryStorageSvc.storeBlob(resourceId, blobId, contentType, dataStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getDeferredListKey() {
|
||||
return myDeferredListKey;
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESHOW_RESOURCES)
|
||||
public void preShow(IPreResourceShowDetails theDetails) throws IOException {
|
||||
long unmarshalledByteCount = 0;
|
||||
|
||||
for (IBaseResource nextResource : theDetails) {
|
||||
|
||||
IIdType resourceId = nextResource.getIdElement();
|
||||
List<IBinaryTarget> attachments = recursivelyScanResourceForBinaryData(nextResource);
|
||||
|
||||
for (IBinaryTarget nextTarget : attachments) {
|
||||
Optional<String> attachmentId = nextTarget.getAttachmentId();
|
||||
if (attachmentId.isPresent()) {
|
||||
|
||||
StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(resourceId, attachmentId.get());
|
||||
if (blobDetails == null) {
|
||||
String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "unknownBlobId");
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
|
||||
if ((unmarshalledByteCount + blobDetails.getBytes()) < myAutoDeExternalizeMaximumBytes) {
|
||||
|
||||
byte[] bytes = myBinaryStorageSvc.fetchBlob(resourceId, attachmentId.get());
|
||||
nextTarget.setData(bytes);
|
||||
unmarshalledByteCount += blobDetails.getBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private List<IBinaryTarget> recursivelyScanResourceForBinaryData(IBaseResource theResource) {
|
||||
List<IBinaryTarget> binaryTargets = new ArrayList<>();
|
||||
myCtx.newTerser().visit(theResource, new IModelVisitor2() {
|
||||
@Override
|
||||
public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
|
||||
|
||||
if (theElement.getClass().equals(myBinaryType)) {
|
||||
IBase parent = theContainingElementPath.get(theContainingElementPath.size() - 2);
|
||||
Optional<IBinaryTarget> binaryTarget = myBinaryAccessProvider.toBinaryTarget(parent);
|
||||
if (binaryTarget.isPresent()) {
|
||||
binaryTargets.add(binaryTarget.get());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return binaryTargets;
|
||||
}
|
||||
|
||||
private static class DeferredBinaryTarget {
|
||||
private final String myBlobId;
|
||||
private final IBinaryTarget myBinaryTarget;
|
||||
private final InputStream myDataStream;
|
||||
|
||||
private DeferredBinaryTarget(String theBlobId, IBinaryTarget theBinaryTarget, byte[] theData) {
|
||||
myBlobId = theBlobId;
|
||||
myBinaryTarget = theBinaryTarget;
|
||||
myDataStream = new ByteArrayInputStream(theData);
|
||||
}
|
||||
|
||||
String getBlobId() {
|
||||
return myBlobId;
|
||||
}
|
||||
|
||||
IBinaryTarget getBinaryTarget() {
|
||||
return myBinaryTarget;
|
||||
}
|
||||
|
||||
InputStream getDataStream() {
|
||||
return myDataStream;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
|
||||
import ca.uhn.fhir.jpa.dao.data.IBinaryStorageEntityDao;
|
||||
import ca.uhn.fhir.jpa.model.entity.BinaryStorageEntity;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import com.google.common.hash.HashingInputStream;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.CountingInputStream;
|
||||
|
@ -57,13 +58,13 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
|
||||
@Override
|
||||
@Transactional(Transactional.TxType.SUPPORTS)
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) {
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) {
|
||||
Date publishedDate = new Date();
|
||||
|
||||
HashingInputStream hashingInputStream = createHashingInputStream(theInputStream);
|
||||
CountingInputStream countingInputStream = createCountingInputStream(hashingInputStream);
|
||||
|
||||
String id = newRandomId();
|
||||
String id = super.provideIdForNewBlob(theBlobIdOrNull);
|
||||
|
||||
BinaryStorageEntity entity = new BinaryStorageEntity();
|
||||
entity.setResourceId(theResourceId.toUnqualifiedVersionless().getValue());
|
||||
|
@ -80,7 +81,7 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
txTemplate.execute(t->{
|
||||
txTemplate.execute(t -> {
|
||||
myEntityManager.persist(entity);
|
||||
return null;
|
||||
});
|
||||
|
@ -88,7 +89,7 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
// Update the entity with the final byte count and hash
|
||||
long bytes = countingInputStream.getCount();
|
||||
String hash = hashingInputStream.hash().toString();
|
||||
txTemplate.execute(t-> {
|
||||
txTemplate.execute(t -> {
|
||||
myBinaryStorageEntityDao.setSize(id, (int) bytes);
|
||||
myBinaryStorageEntityDao.setHash(id, hash);
|
||||
return null;
|
||||
|
@ -126,12 +127,7 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream inputStream = entityOpt.get().getBlob().getBinaryStream();
|
||||
IOUtils.copy(inputStream, theOutputStream);
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
copyBlobToOutputStream(theOutputStream, entityOpt.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -139,6 +135,32 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
@Override
|
||||
public void expungeBlob(IIdType theResourceId, String theBlobId) {
|
||||
Optional<BinaryStorageEntity> entityOpt = myBinaryStorageEntityDao.findByIdAndResourceId(theBlobId, theResourceId.toUnqualifiedVersionless().getValue());
|
||||
entityOpt.ifPresent(theBinaryStorageEntity -> myBinaryStorageEntityDao.delete(theBinaryStorageEntity));
|
||||
entityOpt.ifPresent(theBinaryStorageEntity -> myBinaryStorageEntityDao.deleteByPid(theBinaryStorageEntity.getBlobId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) throws IOException {
|
||||
BinaryStorageEntity entityOpt = myBinaryStorageEntityDao
|
||||
.findByIdAndResourceId(theBlobId, theResourceId.toUnqualifiedVersionless().getValue())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Unknown blob ID: " + theBlobId + " for resource ID " + theResourceId));
|
||||
|
||||
return copyBlobToByteArray(entityOpt);
|
||||
}
|
||||
|
||||
void copyBlobToOutputStream(OutputStream theOutputStream, BinaryStorageEntity theEntity) throws IOException {
|
||||
try (InputStream inputStream = theEntity.getBlob().getBinaryStream()) {
|
||||
IOUtils.copy(inputStream, theOutputStream);
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] copyBlobToByteArray(BinaryStorageEntity theEntity) throws IOException {
|
||||
int size = theEntity.getSize();
|
||||
try {
|
||||
return IOUtils.toByteArray(theEntity.getBlob().getBinaryStream(), size);
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
@ -31,6 +32,7 @@ import org.apache.commons.io.IOUtils;
|
|||
import org.apache.commons.io.input.CountingInputStream;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -63,8 +65,8 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException {
|
||||
String id = newRandomId();
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) throws IOException {
|
||||
String id = super.provideIdForNewBlob(theBlobIdOrNull);
|
||||
File storagePath = getStoragePath(id, true);
|
||||
|
||||
// Write binary file
|
||||
|
@ -111,17 +113,31 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
|
||||
@Override
|
||||
public boolean writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException {
|
||||
InputStream inputStream = getInputStream(theResourceId, theBlobId);
|
||||
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
IOUtils.copy(inputStream, theOutputStream);
|
||||
theOutputStream.close();
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private InputStream getInputStream(IIdType theResourceId, String theBlobId) throws FileNotFoundException {
|
||||
File storagePath = getStoragePath(theBlobId, false);
|
||||
InputStream inputStream = null;
|
||||
if (storagePath != null) {
|
||||
File file = getStorageFilename(storagePath, theResourceId, theBlobId);
|
||||
if (file.exists()) {
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
IOUtils.copy(inputStream, theOutputStream);
|
||||
theOutputStream.close();
|
||||
}
|
||||
inputStream = new FileInputStream(file);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -139,6 +155,20 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) throws IOException {
|
||||
StoredDetails details = fetchBlobDetails(theResourceId, theBlobId);
|
||||
try (InputStream inputStream = getInputStream(theResourceId, theBlobId)) {
|
||||
|
||||
if (inputStream != null) {
|
||||
return IOUtils.toByteArray(inputStream, details.getBytes());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new ResourceNotFoundException("Unknown blob ID: " + theBlobId + " for resource ID " + theResourceId);
|
||||
}
|
||||
|
||||
private void delete(File theStorageFile, String theBlobId) {
|
||||
Validate.isTrue(theStorageFile.delete(), "Failed to delete file for blob %s", theBlobId);
|
||||
}
|
||||
|
@ -164,7 +194,7 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
|
|||
private File getStoragePath(String theId, boolean theCreate) {
|
||||
File path = myBasePath;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
path = new File(path, theId.substring(i, i+1));
|
||||
path = new File(path, theId.substring(i, i + 1));
|
||||
if (!path.exists()) {
|
||||
if (theCreate) {
|
||||
mkdir(path);
|
||||
|
|
|
@ -20,22 +20,12 @@ package ca.uhn.fhir.jpa.binstore;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.util.JsonDateDeserializer;
|
||||
import ca.uhn.fhir.jpa.util.JsonDateSerializer;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.google.common.hash.HashingInputStream;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
|
||||
public interface IBinaryStorageSvc {
|
||||
|
||||
|
@ -77,15 +67,22 @@ public interface IBinaryStorageSvc {
|
|||
*/
|
||||
boolean shouldStoreBlob(long theSize, IIdType theResourceId, String theContentType);
|
||||
|
||||
/**
|
||||
* Generate a new blob ID that will be passed to {@link #storeBlob(IIdType, String, String, InputStream)} later
|
||||
*/
|
||||
String newBlobId();
|
||||
|
||||
/**
|
||||
* Store a new binary blob
|
||||
*
|
||||
* @param theResourceId The resource ID that owns this blob. Note that it should not be possible to retrieve a blob without both the resource ID and the blob ID being correct.
|
||||
* @param theContentType The content type to associate with this blob
|
||||
* @param theInputStream An InputStream to read from. This method should close the stream when it has been fully consumed.
|
||||
* @param theResourceId The resource ID that owns this blob. Note that it should not be possible to retrieve a blob without both the resource ID and the blob ID being correct.
|
||||
* @param theBlobIdOrNull If set, forces
|
||||
* @param theContentType The content type to associate with this blob
|
||||
* @param theInputStream An InputStream to read from. This method should close the stream when it has been fully consumed.
|
||||
* @return Returns details about the stored data
|
||||
*/
|
||||
StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException;
|
||||
@Nonnull
|
||||
StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) throws IOException;
|
||||
|
||||
StoredDetails fetchBlobDetails(IIdType theResourceId, String theBlobId) throws IOException;
|
||||
|
||||
|
@ -96,100 +93,12 @@ public interface IBinaryStorageSvc {
|
|||
|
||||
void expungeBlob(IIdType theResourceId, String theBlobId);
|
||||
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
class StoredDetails {
|
||||
|
||||
@JsonProperty("blobId")
|
||||
private String myBlobId;
|
||||
@JsonProperty("bytes")
|
||||
private long myBytes;
|
||||
@JsonProperty("contentType")
|
||||
private String myContentType;
|
||||
@JsonProperty("hash")
|
||||
private String myHash;
|
||||
@JsonProperty("published")
|
||||
@JsonSerialize(using = JsonDateSerializer.class)
|
||||
@JsonDeserialize(using = JsonDateDeserializer.class)
|
||||
private Date myPublished;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public StoredDetails() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public StoredDetails(@Nonnull String theBlobId, long theBytes, @Nonnull String theContentType, HashingInputStream theIs, Date thePublished) {
|
||||
myBlobId = theBlobId;
|
||||
myBytes = theBytes;
|
||||
myContentType = theContentType;
|
||||
myHash = theIs.hash().toString();
|
||||
myPublished = thePublished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("blobId", myBlobId)
|
||||
.append("bytes", myBytes)
|
||||
.append("contentType", myContentType)
|
||||
.append("hash", myHash)
|
||||
.append("published", myPublished)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return myHash;
|
||||
}
|
||||
|
||||
public StoredDetails setHash(String theHash) {
|
||||
myHash = theHash;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getPublished() {
|
||||
return myPublished;
|
||||
}
|
||||
|
||||
public StoredDetails setPublished(Date thePublished) {
|
||||
myPublished = thePublished;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getContentType() {
|
||||
return myContentType;
|
||||
}
|
||||
|
||||
public StoredDetails setContentType(String theContentType) {
|
||||
myContentType = theContentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getBlobId() {
|
||||
return myBlobId;
|
||||
}
|
||||
|
||||
public StoredDetails setBlobId(String theBlobId) {
|
||||
myBlobId = theBlobId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getBytes() {
|
||||
return myBytes;
|
||||
}
|
||||
|
||||
public StoredDetails setBytes(long theBytes) {
|
||||
myBytes = theBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Fetch the contents of the given blob
|
||||
*
|
||||
* @param theResourceId The resource ID
|
||||
* @param theBlobId The blob ID
|
||||
* @return The payload as a byte array
|
||||
*/
|
||||
byte[] fetchBlob(IIdType theResourceId, String theBlobId) throws IOException;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package ca.uhn.fhir.jpa.binstore;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/**
|
||||
* Wraps an Attachment datatype or Binary resource, since they both
|
||||
* hold binary content but don't look entirely similar
|
||||
*/
|
||||
interface IBinaryTarget {
|
||||
|
||||
void setSize(Integer theSize);
|
||||
|
||||
String getContentType();
|
||||
|
||||
void setContentType(String theContentType);
|
||||
|
||||
byte[] getData();
|
||||
|
||||
void setData(byte[] theBytes);
|
||||
|
||||
IBaseHasExtensions getTarget();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default Optional<String> getAttachmentId() {
|
||||
return getTarget()
|
||||
.getExtension()
|
||||
.stream()
|
||||
.filter(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
|
||||
.filter(t -> t.getValue() instanceof IPrimitiveType)
|
||||
.map(t -> (IPrimitiveType<String>) t.getValue())
|
||||
.map(t -> t.getValue())
|
||||
.filter(t -> isNotBlank(t))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
|
@ -49,8 +49,8 @@ public class MemoryBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl impleme
|
|||
}
|
||||
|
||||
@Override
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException {
|
||||
String id = newRandomId();
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) throws IOException {
|
||||
String id = super.provideIdForNewBlob(theBlobIdOrNull);
|
||||
String key = toKey(theResourceId, id);
|
||||
|
||||
HashingInputStream hashingIs = createHashingInputStream(theInputStream);
|
||||
|
@ -88,8 +88,18 @@ public class MemoryBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl impleme
|
|||
myDetailsMap.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) {
|
||||
String key = toKey(theResourceId, theBlobId);
|
||||
return myDataMap.get(key);
|
||||
}
|
||||
|
||||
private String toKey(IIdType theResourceId, String theBlobId) {
|
||||
return theBlobId + '-' + theResourceId.toUnqualifiedVersionless().getValue();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
myDetailsMap.clear();
|
||||
myDataMap.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,12 @@ public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
}
|
||||
|
||||
@Override
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) {
|
||||
public String newBlobId() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
@ -71,4 +76,9 @@ public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc {
|
|||
public void expungeBlob(IIdType theIdElement, String theBlobId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
package ca.uhn.fhir.jpa.binstore;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2019 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.util.JsonDateDeserializer;
|
||||
import ca.uhn.fhir.jpa.util.JsonDateSerializer;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.google.common.hash.HashingInputStream;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Date;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
public class StoredDetails {
|
||||
|
||||
@JsonProperty("blobId")
|
||||
private String myBlobId;
|
||||
@JsonProperty("bytes")
|
||||
private long myBytes;
|
||||
@JsonProperty("contentType")
|
||||
private String myContentType;
|
||||
@JsonProperty("hash")
|
||||
private String myHash;
|
||||
@JsonProperty("published")
|
||||
@JsonSerialize(using = JsonDateSerializer.class)
|
||||
@JsonDeserialize(using = JsonDateDeserializer.class)
|
||||
private Date myPublished;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public StoredDetails() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public StoredDetails(@Nonnull String theBlobId, long theBytes, @Nonnull String theContentType, HashingInputStream theIs, Date thePublished) {
|
||||
myBlobId = theBlobId;
|
||||
myBytes = theBytes;
|
||||
myContentType = theContentType;
|
||||
myHash = theIs.hash().toString();
|
||||
myPublished = thePublished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("blobId", myBlobId)
|
||||
.append("bytes", myBytes)
|
||||
.append("contentType", myContentType)
|
||||
.append("hash", myHash)
|
||||
.append("published", myPublished)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return myHash;
|
||||
}
|
||||
|
||||
public StoredDetails setHash(String theHash) {
|
||||
myHash = theHash;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getPublished() {
|
||||
return myPublished;
|
||||
}
|
||||
|
||||
public StoredDetails setPublished(Date thePublished) {
|
||||
myPublished = thePublished;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getContentType() {
|
||||
return myContentType;
|
||||
}
|
||||
|
||||
public StoredDetails setContentType(String theContentType) {
|
||||
myContentType = theContentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getBlobId() {
|
||||
return myBlobId;
|
||||
}
|
||||
|
||||
public StoredDetails setBlobId(String theBlobId) {
|
||||
myBlobId = theBlobId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getBytes() {
|
||||
return myBytes;
|
||||
}
|
||||
|
||||
public StoredDetails setBytes(long theBytes) {
|
||||
myBytes = theBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue