Merge branch 'master' into ja_20190928_rationalize_search_param_extractor

This commit is contained in:
jamesagnew 2019-10-25 05:56:39 -04:00
commit cd84af30b1
2377 changed files with 25437 additions and 488560 deletions

View File

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

View File

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

View File

@ -1,15 +1,13 @@
package example;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.rest.annotation.Search;
import org.hl7.fhir.r4.model.InstantType;
import org.hl7.fhir.r4.model.Patient;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.annotation.Search;
public class ServerMetadataExamples {
// START SNIPPET: serverMethod
@ -21,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

View File

@ -36,9 +36,12 @@ public class ServerOperations {
public void manualInputAndOutput(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws IOException {
String contentType = theServletRequest.getContentType();
byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream());
ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length);
// In a real example we might do something more interesting with the received bytes,
// here we'll just replace them with hardcoded ones
bytes = new byte[] { 0, 1, 2, 3 };
theServletResponse.setContentType(contentType);
theServletResponse.getOutputStream().write(bytes);
theServletResponse.getOutputStream().close();

View File

@ -265,7 +265,7 @@ public class ValidatorExamples {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) {
// TODO: implement
return null;
}

View File

@ -100,10 +100,6 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<!-- Logging -->
<dependency>
@ -120,6 +116,11 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -20,6 +20,7 @@ import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.VersionUtil;
import ca.uhn.fhir.validation.FhirValidator;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.jena.riot.Lang;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
@ -898,6 +899,11 @@ public class FhirContext {
}
}
@Override
public String toString() {
return "FhirContext[" + myVersion.getVersion().name() + "]";
}
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2}
*/

View File

@ -29,6 +29,7 @@ import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.util.ReflectionUtil;
import org.hl7.fhir.instance.model.api.*;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
@ -44,56 +45,47 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
class ModelScanner {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<>();
private FhirContext myContext;
private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
private Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinitions = new HashMap<String, BaseRuntimeElementDefinition<?>>();
private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<String, RuntimeResourceDefinition>();
private Map<String, Class<? extends IBaseResource>> myNameToResourceType = new HashMap<String, Class<? extends IBaseResource>>();
private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = new HashMap<>();
private Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinitions = new HashMap<>();
private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<>();
private Map<String, Class<? extends IBaseResource>> myNameToResourceType = new HashMap<>();
private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private Set<Class<? extends IBase>> myScanAlso = new HashSet<Class<? extends IBase>>();
private Set<Class<? extends IBase>> myScanAlso = new HashSet<>();
private FhirVersionEnum myVersion;
private Set<Class<? extends IBase>> myVersionTypes;
ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions,
Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
@Nonnull Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
myContext = theContext;
myVersion = theVersion;
Set<Class<? extends IBase>> toScan;
if (theResourceTypes != null) {
toScan = new HashSet<Class<? extends IBase>>(theResourceTypes);
} else {
toScan = new HashSet<Class<? extends IBase>>();
}
Set<Class<? extends IBase>> toScan = new HashSet<>(theResourceTypes);
init(theExistingDefinitions, toScan);
}
public Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
return myClassToElementDefinitions;
}
public Map<String, RuntimeResourceDefinition> getIdToResourceDefinition() {
Map<String, RuntimeResourceDefinition> getIdToResourceDefinition() {
return myIdToResourceDefinition;
}
public Map<String, BaseRuntimeElementDefinition<?>> getNameToElementDefinitions() {
Map<String, BaseRuntimeElementDefinition<?>> getNameToElementDefinitions() {
return myNameToElementDefinitions;
}
public Map<String, RuntimeResourceDefinition> getNameToResourceDefinition() {
Map<String, RuntimeResourceDefinition> getNameToResourceDefinition() {
return myNameToResourceDefinitions;
}
public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() {
return (myNameToResourceDefinitions);
}
public Map<String, Class<? extends IBaseResource>> getNameToResourceType() {
Map<String, Class<? extends IBaseResource>> getNameToResourceType() {
return myNameToResourceType;
}
public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
return myRuntimeChildUndeclaredExtensionDefinition;
}
@ -145,11 +137,10 @@ class ModelScanner {
}
private boolean isStandardType(Class<? extends IBase> theClass) {
boolean retVal = myVersionTypes.contains(theClass);
return retVal;
return myVersionTypes.contains(theClass);
}
private void scan(Class<? extends IBase> theClass) throws ConfigurationException {
void scan(Class<? extends IBase> theClass) throws ConfigurationException {
BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass);
if (existingDef != null) {
return;
@ -204,9 +195,6 @@ class ModelScanner {
ourLog.debug("Scanning resource block class: {}", theClass.getName());
String resourceName = theClass.getCanonicalName();
if (isBlank(resourceName)) {
throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
}
// Just in case someone messes up when upgrading from DSTU2
if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
@ -341,14 +329,14 @@ class ModelScanner {
private void scanResourceForSearchParams(Class<? extends IBaseResource> theClass, RuntimeResourceDefinition theResourceDef) {
Map<String, RuntimeSearchParam> nameToParam = new HashMap<String, RuntimeSearchParam>();
Map<Field, SearchParamDefinition> compositeFields = new LinkedHashMap<Field, SearchParamDefinition>();
Map<String, RuntimeSearchParam> nameToParam = new HashMap<>();
Map<Field, SearchParamDefinition> compositeFields = new LinkedHashMap<>();
/*
* Make sure we pick up fields in interfaces too.. This ensures that we
* grab the _id field which generally gets picked up via interface
*/
Set<Field> fields = new HashSet<Field>(Arrays.asList(theClass.getFields()));
Set<Field> fields = new HashSet<>(Arrays.asList(theClass.getFields()));
Class<?> nextClass = theClass;
do {
for (Class<?> nextInterface : nextClass.getInterfaces()) {
@ -400,12 +388,12 @@ class ModelScanner {
for (Entry<Field, SearchParamDefinition> nextEntry : compositeFields.entrySet()) {
SearchParamDefinition searchParam = nextEntry.getValue();
List<RuntimeSearchParam> compositeOf = new ArrayList<RuntimeSearchParam>();
List<RuntimeSearchParam> compositeOf = new ArrayList<>();
for (String nextName : searchParam.compositeOf()) {
RuntimeSearchParam param = nameToParam.get(nextName);
if (param == null) {
ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}",
new Object[]{theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()});
theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet());
continue;
}
compositeOf.add(param);
@ -417,7 +405,7 @@ class ModelScanner {
}
private Set<String> toTargetList(Class<? extends IBaseResource>[] theTarget) {
HashSet<String> retVal = new HashSet<String>();
HashSet<String> retVal = new HashSet<>();
for (Class<? extends IBaseResource> nextType : theTarget) {
ResourceDef resourceDef = nextType.getAnnotation(ResourceDef.class);
@ -486,7 +474,7 @@ class ModelScanner {
}
static Set<Class<? extends IBase>> scanVersionPropertyFile(Set<Class<? extends IBase>> theDatatypes, Map<String, Class<? extends IBaseResource>> theResourceTypes, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingElementDefinitions) {
Set<Class<? extends IBase>> retVal = new HashSet<Class<? extends IBase>>();
Set<Class<? extends IBase>> retVal = new HashSet<>();
try (InputStream str = theVersion.getVersionImplementation().getFhirVersionPropertiesFile()) {
Properties prop = new Properties();

View File

@ -1,100 +0,0 @@
package ca.uhn.fhir.context;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.api.annotation.ProvidesResources;
/**
* Scans a class tagged with {@code ProvidesResources} and adds any resources listed to its FhirContext's resource
* definition list. This makes the profile generator find the classes.
*
* @see ca.uhn.fhir.model.api.annotation.ProvidesResources
*/
public class ProvidedResourceScanner {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ProvidedResourceScanner.class);
private FhirContext myContext;
/**
* Constructor
*
* @param theContext
* - context whose resource definition list is to be updated by the scanner
*/
public ProvidedResourceScanner(FhirContext theContext) {
myContext = theContext;
}
/**
* If {@code theProvider} is tagged with the {@code ProvidesResources} annotation, this method will add every
* resource listed by the {@code resources} method.
* <p>
* Notes:
* </p>
* <ul>
* <li>if {@code theProvider} isn't annotated with {@code resources} nothing is done; it's expected that most
* RestfulServers and ResourceProviders won't be annotated.</li>
* <li>any object listed in {@code resources} that doesn't implement {@code IResource} will generate a warning in the
* log.</li>
* </ul>
*
* @param theProvider
* - Normally, either a {@link ca.uhn.fhir.rest.server.RestfulServer} or a
* {@link ca.uhn.fhir.rest.server.IResourceProvider} that might be annotated with
* {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
*/
@SuppressWarnings("unchecked")
public void scanForProvidedResources(Object theProvider) {
ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
if (annotation == null)
return;
for (Class<?> clazz : annotation.resources()) {
if (IBaseResource.class.isAssignableFrom(clazz)) {
myContext.getResourceDefinition((Class<? extends IBaseResource>) clazz);
} else {
ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
}
}
}
/**
* Remove any metadata that was added by any {@code ProvidesResources} annotation
* present in {@code theProvider}. This method is callled from {@code RestfulService}
* when it is unregistering a Resource Provider.
*
* @param theProvider
* - Normally a {@link ca.uhn.fhir.rest.server.IResourceProvider} that might
* be annotated with {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
*/
public void removeProvidedResources(Object theProvider) {
ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
if (annotation == null)
return;
for (Class<?> clazz : annotation.resources()) {
if (IBaseResource.class.isAssignableFrom(clazz)) {
// TODO -- not currently used but should be finished for completeness
} else {
ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
}
}
}
}

View File

@ -102,6 +102,11 @@ public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST>
*/
boolean isCodeSystemSupported(FhirContext theContext, String theSystem);
/**
* Fetch the given ValueSet by URL
*/
IBaseResource fetchValueSet(FhirContext theContext, String theValueSetUrl);
/**
* Validates that the given code exists and if possible returns a display
* name. This method is called to check codes which are found in "example"
@ -112,7 +117,7 @@ public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST>
* @param theDisplay The display name, if it should also be validated
* @return Returns a validation result object
*/
CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl);
/**
* Look up a code using the system and code value

View File

@ -1,64 +0,0 @@
package ca.uhn.fhir.model.api;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.util.ElementUtil;
public class BaseBundle extends BaseElement /*implements IElement*/ {
private static final long serialVersionUID = 3349586533271409727L;
private StringDt myAuthorName;
private StringDt myAuthorUri;
private IdDt myId;
public StringDt getAuthorName() {
if (myAuthorName == null) {
myAuthorName = new StringDt();
}
return myAuthorName;
}
public StringDt getAuthorUri() {
if (myAuthorUri == null) {
myAuthorUri = new StringDt();
}
return myAuthorUri;
}
public IdDt getId() {
if (myId==null) {
myId=new IdDt();
}
return myId;
}
@Override
public boolean isEmpty() {
return ElementUtil.isEmpty(myAuthorName, myAuthorUri);
}
public void setId(IdDt theId) {
myId = theId;
}
}

View File

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

View File

@ -21,7 +21,6 @@ package ca.uhn.fhir.model.api;
*/
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
@ -29,7 +28,6 @@ import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.io.Serializable;
@ -92,29 +90,7 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
theResource.setUserData(DELETED_AT.name(), theObject);
}
};
/**
* Denotes the search score which a given resource should match in a transaction. See the FHIR transaction definition for information about this. Corresponds to the value in
* <code>Bundle.entry.score</code> in a Bundle resource.
* <p>
* Note that search URL is only used in FHIR DSTU2 and later.
* </p>
* <p>
* Values for this key are of type <b>{@link DecimalDt}</b>
* </p>
*/
public static final ResourceMetadataKeyEnum<DecimalDt> ENTRY_SCORE = new ResourceMetadataKeyEnum<DecimalDt>("ENTRY_SCORE") {
private static final long serialVersionUID = 1L;
@Override
public DecimalDt get(IResource theResource) {
return getDecimalFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ENTRY_SCORE);
}
@Override
public void put(IResource theResource, DecimalDt theObject) {
theResource.getResourceMetadata().put(ENTRY_SCORE, theObject);
}
};
/**
* If present and populated with a {@link BundleEntrySearchModeEnum}, contains the "bundle entry search mode", which is the value of the status field in the Bundle entry containing this resource.
* The value for this key corresponds to field <code>Bundle.entry.search.mode</code>. This value can be set to provide a status value of "include" for included resources being returned by a
@ -187,67 +163,7 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
}
};
/**
* If present and populated with a string, provides the "alternate link" (the link element in the bundle entry with <code>rel="alternate"</code>). Server implementations may populate this with a
* complete URL, in which case the URL will be placed as-is in the bundle. They may alternately specify a resource relative URL (e.g. "Patient/1243") in which case the server will convert this to
* an absolute URL at runtime.
* <p>
* Values for this key are of type <b>{@link String}</b>
* </p>
*/
public static final ResourceMetadataKeyEnum<String> LINK_ALTERNATE = new ResourceMetadataKeyEnum<String>("LINK_ALTERNATE") {
private static final long serialVersionUID = 1L;
@Override
public String get(IResource theResource) {
return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), LINK_ALTERNATE);
}
@Override
public void put(IResource theResource, String theObject) {
theResource.getResourceMetadata().put(LINK_ALTERNATE, theObject);
}
};
/**
* If present and populated with a string, provides the "search link" (the link element in the bundle entry with <code>rel="search"</code>). Server implementations may populate this with a
* complete URL, in which case the URL will be placed as-is in the bundle. They may alternately specify a resource relative URL (e.g. "Patient?name=tester") in which case the server will convert
* this to an absolute URL at runtime.
* <p>
* Values for this key are of type <b>{@link String}</b>
* </p>
*/
public static final ResourceMetadataKeyEnum<String> LINK_SEARCH = new ResourceMetadataKeyEnum<String>("LINK_SEARCH") {
private static final long serialVersionUID = 1L;
@Override
public String get(IResource theResource) {
return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), LINK_SEARCH);
}
@Override
public void put(IResource theResource, String theObject) {
theResource.getResourceMetadata().put(LINK_SEARCH, theObject);
}
};
/**
* The value for this key represents a previous ID used to identify this resource. This key is currently only used internally during transaction method processing.
* <p>
* Values for this key are of type <b>{@link IdDt}</b>
* </p>
*/
public static final ResourceMetadataKeyEnum<IdDt> PREVIOUS_ID = new ResourceMetadataKeyEnum<IdDt>("PREVIOUS_ID") {
private static final long serialVersionUID = 1L;
@Override
public IdDt get(IResource theResource) {
return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata(), PREVIOUS_ID);
}
@Override
public void put(IResource theResource, IdDt theObject) {
theResource.getResourceMetadata().put(PREVIOUS_ID, theObject);
}
};
/**
* The value for this key represents a {@link List} of profile IDs that this resource claims to conform to.
* <p>
@ -301,18 +217,8 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
if (obj == null) {
return null;
}
try {
@SuppressWarnings("unchecked")
List<BaseCodingDt> securityLabels = (List<BaseCodingDt>) obj;
if (securityLabels.isEmpty()) {
return null;
}
return securityLabels;
} catch (ClassCastException e) {
throw new InternalErrorException("Found an object of type '" + obj.getClass().getCanonicalName() + "' in resource metadata for key SECURITY_LABELS - Expected "
+ BaseCodingDt.class.getCanonicalName());
}
//noinspection unchecked
return (List<BaseCodingDt>) obj;
}
@Override
@ -337,14 +243,9 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
Object retValObj = theResource.getResourceMetadata().get(TAG_LIST);
if (retValObj == null) {
return null;
} else if (retValObj instanceof TagList) {
if (((TagList) retValObj).isEmpty()) {
return null;
}
} else {
return (TagList) retValObj;
}
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + TAG_LIST.name() + " - Expected "
+ TagList.class.getCanonicalName());
}
@Override
@ -352,25 +253,6 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
theResource.getResourceMetadata().put(TAG_LIST, theObject);
}
};
/**
* If present and populated with a string (as an instance of {@link String}), this value contains the title for this resource, as supplied in any bundles containing the resource.
* <p>
* Values for this key are of type <b>{@link String}</b>
* </p>
*/
public static final ResourceMetadataKeyEnum<String> TITLE = new ResourceMetadataKeyEnum<String>("TITLE") {
private static final long serialVersionUID = 1L;
@Override
public String get(IResource theResource) {
return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), TITLE);
}
@Override
public void put(IResource theResource, String theObject) {
theResource.getResourceMetadata().put(TITLE, theObject);
}
};
/**
* The value for this key is the bundle entry <b>Updated</b> time. This is defined by FHIR as "Last Updated for resource". This value is also used for populating the "Last-Modified" header in the
* case of methods that return a single resource (read, vread, etc.)
@ -398,7 +280,10 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
* <p>
* Values for this key are of type <b>{@link String}</b>
* </p>
*
* @deprecated The {@link IResource#getId()} resource ID will now be populated with the version ID via the {@link IdDt#getVersionIdPart()} method
*/
@Deprecated
public static final ResourceMetadataKeyEnum<String> VERSION = new ResourceMetadataKeyEnum<String>("VERSION") {
private static final long serialVersionUID = 1L;
@ -426,7 +311,7 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
@Override
public IdDt get(IResource theResource) {
return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata(), VERSION_ID);
return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata());
}
@Override
@ -474,32 +359,45 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
public abstract void put(IResource theResource, T theObject);
@Override
public String toString() {
return myValue;
public static abstract class ResourceMetadataKeySupportingAnyResource<T, T2> extends ResourceMetadataKeyEnum<T> {
private static final long serialVersionUID = 1L;
public ResourceMetadataKeySupportingAnyResource(String theValue) {
super(theValue);
}
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);
}
}
}

View File

@ -168,18 +168,6 @@ public class Tag extends BaseElement implements IElement, IBaseCoding {
return this;
}
public String toHeaderValue() {
StringBuilder b = new StringBuilder();
b.append(this.getTerm());
if (isNotBlank(this.getLabel())) {
b.append("; label=\"").append(this.getLabel()).append('"');
}
if (isNotBlank(this.getScheme())) {
b.append("; scheme=\"").append(this.getScheme()).append('"');
}
return b.toString();
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);

View File

@ -1,43 +0,0 @@
package ca.uhn.fhir.model.api.annotation;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* IResourceProvider and RestfulServer subclasses can use this annotation to designate which custom resources they can provide.
* These resources will automatically be added to the resource list used for profile generation.
* <pre>
* Examples:
* {@literal @}ProvidesResources(resource=CustomObservation.class)
* class CustomObservationResourceProvider implements IResourceProvider{...}
*
* {@literal @}ProvidesResources(resource={CustomPatient.class,CustomObservation.class}){...}
* class FhirServer extends RestfulServer
* }
* </pre>
* Note that you needn't annotate both the IResourceProvider and the RestfulServer for a given resource; either one will suffice.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvidesResources {
Class<?>[] resources();
}

View File

@ -36,5 +36,7 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
public @interface Metadata {
// nothing for now
long cacheMillis() default 60 * 1000L;
}

View File

@ -26,7 +26,9 @@ import java.util.*;
public class Constants {
public static final String CT_TEXT_CSV = "text/csv";
public static final String HEADER_REQUEST_ID = "X-Request-ID";
public static final String HEADER_REQUEST_SOURCE = "X-Request-Source";
public static final String CACHE_CONTROL_MAX_RESULTS = "max-results";
public static final String CACHE_CONTROL_NO_CACHE = "no-cache";
public static final String CACHE_CONTROL_NO_STORE = "no-store";
@ -241,6 +243,15 @@ public class Constants {
* Operation name for the $lastn operation
*/
public static final String OPERATION_LASTN = "$lastn";
/**
* <p>
* This extension represents the equivalent of the
* <code>Resource.meta.source</code> field within R4+ resources, and is for
* use in DSTU3 resources. It should contain a value of type <code>uri</code>
* and will be located on the Resource.meta
* </p>
*/
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
static {
CHARSET_UTF8 = StandardCharsets.UTF_8;

View File

@ -1,5 +1,12 @@
package ca.uhn.fhir.rest.api;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
import java.util.ArrayList;
import java.util.StringTokenizer;
import static org.apache.commons.lang3.StringUtils.isBlank;
/*
@ -22,13 +29,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* #L%
*/
import java.util.ArrayList;
import java.util.StringTokenizer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
public class QualifiedParamList extends ArrayList<String> {
private static final long serialVersionUID = 1L;
@ -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;

View File

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

View File

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

View File

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

View File

@ -33,12 +33,6 @@ import java.util.Map;
*/
public interface IHttpResponse {
/**
* @deprecated This method was deprecated in HAPI FHIR 2.2 because its name has a typo. Use {@link #bufferEntity()} instead.
*/
@Deprecated
void bufferEntitity() throws IOException;
/**
* Buffer the message entity data.
* <p>

View File

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

View File

@ -248,26 +248,30 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ {
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (this == theO) {
return true;
}
if (theO == null || getClass() != theO.getClass()) return false;
if (theO == null || getClass() != theO.getClass()) {
return false;
}
TokenParam that = (TokenParam) theO;
return new EqualsBuilder()
.append(myModifier, that.myModifier)
.append(mySystem, that.mySystem)
.append(myValue, that.myValue)
.isEquals();
EqualsBuilder b = new EqualsBuilder();
b.append(myModifier, that.myModifier);
b.append(mySystem, that.mySystem);
b.append(myValue, that.myValue);
return b.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myModifier)
.append(mySystem)
.append(myValue)
.toHashCode();
HashCodeBuilder b = new HashCodeBuilder(17, 37);
b.append(myModifier);
b.append(mySystem);
b.append(myValue);
return b.toHashCode();
}
private static String toSystemValue(UriDt theSystem) {

View File

@ -0,0 +1,74 @@
package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class AsyncUtil {
private static final Logger ourLog = LoggerFactory.getLogger(AsyncUtil.class);
/**
* Non instantiable
*/
private AsyncUtil() {
}
/**
* Calls Thread.sleep and if an InterruptedException occurs, logs a warning but otherwise continues
*
* @param theMillis The number of millis to sleep
* @return Did we sleep the whole amount
*/
public static boolean sleep(long theMillis) {
try {
Thread.sleep(theMillis);
return true;
} catch (InterruptedException theE) {
Thread.currentThread().interrupt();
ourLog.warn("Sleep for {}ms was interrupted", theMillis);
return false;
}
}
public static boolean awaitLatchAndThrowInternalErrorExceptionOnInterrupt(CountDownLatch theInitialCollectionLatch, long theTime, TimeUnit theTimeUnit) {
try {
return theInitialCollectionLatch.await(theTime, theTimeUnit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new InternalErrorException(e);
}
}
public static boolean awaitLatchAndIgnoreInterrupt(CountDownLatch theInitialCollectionLatch, long theTime, TimeUnit theTimeUnit) {
try {
return theInitialCollectionLatch.await(theTime, theTimeUnit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
ourLog.warn("Interrupted while waiting for latch");
return false;
}
}
}

View File

@ -20,9 +20,8 @@ package ca.uhn.fhir.util;
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.*;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -35,12 +34,10 @@ public class AttachmentUtil {
* Fetches the base64Binary value of Attachment.data, creating it if it does not
* already exist.
*/
@SuppressWarnings("unchecked")
public static IPrimitiveType<byte[]> getOrCreateData(FhirContext theContext, ICompositeType theAttachment) {
return getOrCreateChild(theContext, theAttachment, "data", "base64Binary");
}
@SuppressWarnings("unchecked")
public static IPrimitiveType<String> getOrCreateContentType(FhirContext theContext, ICompositeType theAttachment) {
return getOrCreateChild(theContext, theAttachment, "contentType", "string");
}
@ -64,6 +61,16 @@ public class AttachmentUtil {
});
}
public static void setUrl(FhirContext theContext, ICompositeType theAttachment, String theUrl) {
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "url");
assert entryChild != null : "Version " + theContext + " has no child " + "url";
String typeName = "uri";
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
typeName = "url";
}
entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, typeName, theUrl));
}
public static void setContentType(FhirContext theContext, ICompositeType theAttachment, String theContentType) {
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "contentType");
entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, "code", theContentType));
@ -88,7 +95,9 @@ public class AttachmentUtil {
*/
@SuppressWarnings("unchecked")
static <T> IPrimitiveType<T> newPrimitive(FhirContext theContext, String theType, T theValue) {
IPrimitiveType<T> primitive = (IPrimitiveType<T>) theContext.getElementDefinition(theType).newInstance();
BaseRuntimeElementDefinition<?> elementDefinition = theContext.getElementDefinition(theType);
Validate.notNull(elementDefinition, "Unknown type %s for %s", theType, theContext);
IPrimitiveType<T> primitive = (IPrimitiveType<T>) elementDefinition.newInstance();
primitive.setValue(theValue);
return primitive;
}
@ -100,4 +109,8 @@ public class AttachmentUtil {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theElement.getClass());
return def.getChildByName(theName);
}
public static ICompositeType newInstance(FhirContext theFhirCtx) {
return (ICompositeType) theFhirCtx.getElementDefinition("Attachment").newInstance();
}
}

View File

@ -1,19 +1,20 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.bundle.BundleEntryMutator;
import ca.uhn.fhir.util.bundle.BundleEntryParts;
import ca.uhn.fhir.util.bundle.EntryListAccumulator;
import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.instance.model.api.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -42,37 +43,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/
public class BundleUtil {
public static class BundleEntryParts {
private final RequestTypeEnum myRequestType;
private final IBaseResource myResource;
private final String myUrl;
private final String myConditionalUrl;
BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource, String theConditionalUrl) {
super();
myRequestType = theRequestType;
myUrl = theUrl;
myResource = theResource;
myConditionalUrl = theConditionalUrl;
}
public RequestTypeEnum getRequestType() {
return myRequestType;
}
public IBaseResource getResource() {
return myResource;
}
public String getConditionalUrl() {
return myConditionalUrl;
}
public String getUrl() {
return myUrl;
}
}
/**
* @return Returns <code>null</code> if the link isn't found or has no value
*/
@ -185,20 +155,25 @@ public class BundleUtil {
* Extract all of the resources from a given bundle
*/
public static List<BundleEntryParts> toListOfEntries(FhirContext theContext, IBaseBundle theBundle) {
List<BundleEntryParts> retVal = new ArrayList<>();
EntryListAccumulator entryListAccumulator = new EntryListAccumulator();
processEntries(theContext, theBundle, entryListAccumulator);
return entryListAccumulator.getList();
}
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
public static void processEntries(FhirContext theContext, IBaseBundle theBundle, Consumer<ModifiableBundleEntry> theProcessor) {
RuntimeResourceDefinition bundleDef = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChildDef = bundleDef.getChildByName("entry");
List<IBase> entries = entryChildDef.getAccessor().getValues(theBundle);
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestElem = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
BaseRuntimeChildDefinition requestUrlChild = requestElem.getChildByName("url");
BaseRuntimeChildDefinition requestIfNoneExistChild = requestElem.getChildByName("ifNoneExist");
BaseRuntimeChildDefinition methodChild = requestElem.getChildByName("method");
BaseRuntimeElementCompositeDefinition<?> entryChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource");
BaseRuntimeChildDefinition requestChildDef = entryChildContentsDef.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) requestChildDef.getChildByName("request");
BaseRuntimeChildDefinition requestUrlChildDef = requestChildContentsDef.getChildByName("url");
BaseRuntimeChildDefinition requestIfNoneExistChildDef = requestChildContentsDef.getChildByName("ifNoneExist");
BaseRuntimeChildDefinition methodChildDef = requestChildContentsDef.getChildByName("method");
for (IBase nextEntry : entries) {
IBaseResource resource = null;
@ -206,15 +181,15 @@ public class BundleUtil {
RequestTypeEnum requestType = null;
String conditionalUrl = null;
for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
resource = (IBaseResource) next;
for (IBase nextResource : resourceChildDef.getAccessor().getValues(nextEntry)) {
resource = (IBaseResource) nextResource;
}
for (IBase nextRequest : requestChild.getAccessor().getValues(nextEntry)) {
for (IBase nextUrl : requestUrlChild.getAccessor().getValues(nextRequest)) {
for (IBase nextRequest : requestChildDef.getAccessor().getValues(nextEntry)) {
for (IBase nextUrl : requestUrlChildDef.getAccessor().getValues(nextRequest)) {
url = ((IPrimitiveType<?>) nextUrl).getValueAsString();
}
for (IBase nextUrl : methodChild.getAccessor().getValues(nextRequest)) {
String methodString = ((IPrimitiveType<?>) nextUrl).getValueAsString();
for (IBase nextMethod : methodChildDef.getAccessor().getValues(nextRequest)) {
String methodString = ((IPrimitiveType<?>) nextMethod).getValueAsString();
if (isNotBlank(methodString)) {
requestType = RequestTypeEnum.valueOf(methodString);
}
@ -227,7 +202,7 @@ public class BundleUtil {
conditionalUrl = url != null && url.contains("?") ? url : null;
break;
case POST:
List<IBase> ifNoneExistReps = requestIfNoneExistChild.getAccessor().getValues(nextRequest);
List<IBase> ifNoneExistReps = requestIfNoneExistChildDef.getAccessor().getValues(nextRequest);
if (ifNoneExistReps.size() > 0) {
IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) ifNoneExistReps.get(0);
conditionalUrl = ifNoneExist.getValueAsString();
@ -241,11 +216,10 @@ public class BundleUtil {
* All 3 might be null - That's ok because we still want to know the
* order in the original bundle.
*/
retVal.add(new BundleEntryParts(requestType, url, resource, conditionalUrl));
BundleEntryMutator mutator = new BundleEntryMutator(nextEntry, requestChildDef, requestChildContentsDef);
ModifiableBundleEntry entry = new ModifiableBundleEntry(new BundleEntryParts(requestType, url, resource, conditionalUrl), mutator);
theProcessor.accept(entry);
}
return retVal;
}
/**
@ -278,4 +252,25 @@ public class BundleUtil {
}
return retVal;
}
/**
* DSTU3 did not allow the PATCH verb for transaction bundles- so instead we infer that a bundle
* is a patch if the payload is a binary resource containing a patch. This method
* tests whether a resource (which should have come from
* <code>Bundle.entry.resource</code> is a Binary resource with a patch
* payload type.
*/
public static boolean isDstu3TransactionPatch(IBaseResource thePayloadResource) {
boolean isPatch = false;
if (thePayloadResource instanceof IBaseBinary) {
String contentType = ((IBaseBinary) thePayloadResource).getContentType();
try {
PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType);
isPatch = true;
} catch (InvalidRequestException e) {
// ignore
}
}
return isPatch;
}
}

View File

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

View File

@ -0,0 +1,38 @@
package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.text.DecimalFormat;
public class FileUtil {
// Use "bytes" instead of just "b" because it reads easier in logs
private static final String[] UNITS = new String[]{"Bytes", "kB", "MB", "GB", "TB"};
public static String formatFileSize(long theBytes) {
if (theBytes <= 0) {
return "0 " + UNITS[0];
}
int digitGroups = (int) (Math.log10(theBytes) / Math.log10(1024));
digitGroups = Math.min(digitGroups, UNITS.length - 1);
return new DecimalFormat("###0.#").format(theBytes / Math.pow(1024, digitGroups)) + " " + UNITS[digitGroups];
}
}

View File

@ -45,7 +45,7 @@ public interface IModelVisitor2 {
/**
*
*/
boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath);
default boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) { return true; }
}

View File

@ -23,9 +23,9 @@ package ca.uhn.fhir.util;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.api.Constants;
import org.hl7.fhir.instance.model.api.*;
import java.util.List;
@ -46,6 +46,29 @@ public class MetaUtil {
return retVal;
}
/**
* Sets the value for <code>Resource.meta.source</code> for R4+ resources, and places the value in
* an extension on <code>Resource.meta</code>
* with the URL <code>http://hapifhir.io/fhir/StructureDefinition/resource-meta-source</code> for DSTU3.
*
* @param theContext The FhirContext object
* @param theResource The resource to modify
* @param theValue The source URI
* @see <a href="http://hl7.org/fhir/resource-definitions.html#Resource.meta">Meta.source</a>
*/
@SuppressWarnings("unchecked")
public static void setSource(FhirContext theContext, IBaseResource theResource, String theValue) {
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
MetaUtil.setSource(theContext, theResource.getMeta(), theValue);
} else if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
IBaseExtension<?, ?> sourceExtension = ((IBaseHasExtensions) theResource.getMeta()).addExtension();
sourceExtension.setUrl(Constants.EXT_META_SOURCE);
IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("uri").newInstance();
value.setValue(theValue);
sourceExtension.setValue(value);
}
}
public static void setSource(FhirContext theContext, IBaseMetaType theMeta, String theValue) {
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theMeta.getClass());
BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");

View File

@ -19,22 +19,17 @@ package ca.uhn.fhir.util;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.List;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Utilities for dealing with OperationOutcome resources across various model versions
@ -43,20 +38,17 @@ public class OperationOutcomeUtil {
/**
* Add an issue to an OperationOutcome
*
* @param theCtx
* The fhir context
* @param theOperationOutcome
* The OO resource to add to
* @param theSeverity
* The severity (fatal | error | warning | information)
* @param theDetails
* The details string
* @param theCtx The fhir context
* @param theOperationOutcome The OO resource to add to
* @param theSeverity The severity (fatal | error | warning | information)
* @param theDetails The details string
* @param theCode
* @return Returns the newly added issue
*/
public static void addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) {
public static IBase addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) {
IBase issue = createIssue(theCtx, theOperationOutcome);
populateDetails(theCtx, issue, theSeverity, theDetails, theLocation, theCode);
return issue;
}
private static IBase createIssue(FhirContext theCtx, IBaseResource theOutcome) {
@ -140,7 +132,6 @@ public class OperationOutcomeUtil {
BaseRuntimeElementDefinition<?> stringDef = detailsChild.getChildByName(detailsChild.getElementName());
BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity");
BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location");
IPrimitiveType<?> severityElem = (IPrimitiveType<?>) severityChild.getChildByName("severity").newInstance(severityChild.getInstanceConstructorArguments());
severityElem.setValueAsString(theSeverity);
@ -150,7 +141,13 @@ public class OperationOutcomeUtil {
string.setValueAsString(theDetails);
detailsChild.getMutator().setValue(theIssue, string);
addLocationToIssue(theCtx, theIssue, theLocation);
}
public static void addLocationToIssue(FhirContext theContext, IBase theIssue, String theLocation) {
if (isNotBlank(theLocation)) {
BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theIssue.getClass());
BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location");
IPrimitiveType<?> locationElem = (IPrimitiveType<?>) locationChild.getChildByName("location").newInstance(locationChild.getInstanceConstructorArguments());
locationElem.setValueAsString(theLocation);
locationChild.getMutator().addValue(theIssue, locationElem);

View File

@ -72,6 +72,8 @@ public class ParametersUtil {
}
private static void addClientParameter(FhirContext theContext, Object theValue, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem, String theName) {
Validate.notNull(theValue, "theValue must not be null");
if (theValue instanceof IBaseResource) {
IBase parameter = createParameterRepetition(theContext, theTargetResource, paramChild, paramChildElem, theName);
paramChildElem.getChildByName("resource").getMutator().addValue(parameter, (IBaseResource) theValue);
@ -162,7 +164,6 @@ public class ParametersUtil {
IPrimitiveType<Boolean> value = (IPrimitiveType<Boolean>) theCtx.getElementDefinition("boolean").newInstance();
value.setValue(theValue);
addParameterToParameters(theCtx, theParameters, theName, value);
}
@SuppressWarnings("unchecked")

View File

@ -51,16 +51,8 @@ public class ReflectionUtil {
}
public static Class<?> getGenericCollectionTypeOfField(Field next) {
Class<?> type;
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
Type firstArg = collectionType.getActualTypeArguments()[0];
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
ParameterizedType pt = ((ParameterizedType) firstArg);
type = (Class<?>) pt.getRawType();
} else {
type = (Class<?>) firstArg;
}
return type;
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
}
/**
@ -84,42 +76,37 @@ public class ReflectionUtil {
}
public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) {
Class<?> type;
Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex];
if (Class.class.equals(genericParameterType) || Class.class.equals(genericParameterType.getClass())) {
return null;
}
ParameterizedType collectionType = (ParameterizedType) genericParameterType;
Type firstArg = collectionType.getActualTypeArguments()[0];
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
ParameterizedType pt = ((ParameterizedType) firstArg);
type = (Class<?>) pt.getRawType();
} else {
type = (Class<?>) firstArg;
}
return type;
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
}
@SuppressWarnings({ "rawtypes" })
public static Class<?> getGenericCollectionTypeOfMethodReturnType(Method theMethod) {
Class<?> type;
Type genericReturnType = theMethod.getGenericReturnType();
if (!(genericReturnType instanceof ParameterizedType)) {
return null;
}
ParameterizedType collectionType = (ParameterizedType) genericReturnType;
Type firstArg = collectionType.getActualTypeArguments()[0];
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
ParameterizedType pt = ((ParameterizedType) firstArg);
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
}
@SuppressWarnings({ "rawtypes" })
private static Class<?> getGenericCollectionTypeOf(Type theType) {
Class<?> type;
if (ParameterizedType.class.isAssignableFrom(theType.getClass())) {
ParameterizedType pt = ((ParameterizedType) theType);
type = (Class<?>) pt.getRawType();
} else if (firstArg instanceof TypeVariable<?>) {
Type decl = ((TypeVariable) firstArg).getBounds()[0];
} else if (theType instanceof TypeVariable<?>) {
Type decl = ((TypeVariable) theType).getBounds()[0];
return (Class<?>) decl;
} else if (firstArg instanceof WildcardType) {
Type decl = ((WildcardType) firstArg).getUpperBounds()[0];
} else if (theType instanceof WildcardType) {
Type decl = ((WildcardType) theType).getUpperBounds()[0];
return (Class<?>) decl;
} else {
type = (Class<?>) firstArg;
type = (Class<?>) theType;
}
return type;
}
@ -154,8 +141,7 @@ public class ReflectionUtil {
public static Object newInstanceOfFhirServerType(String theType) {
String errorMessage = "Unable to instantiate server framework. Please make sure that hapi-fhir-server library is on your classpath!";
String wantedType = "ca.uhn.fhir.rest.api.server.IFhirVersionServer";
Object fhirServerVersion = newInstanceOfType(theType, errorMessage, wantedType);
return fhirServerVersion;
return newInstanceOfType(theType, errorMessage, wantedType);
}
@SuppressWarnings("unchecked")

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.util;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import java.text.DecimalFormat;
@ -9,8 +10,6 @@ import java.util.Date;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR - Core Library
@ -48,12 +47,14 @@ public class StopWatch {
private long myStarted = now();
private TaskTiming myCurrentTask;
private LinkedList<TaskTiming> myTasks;
/**
* Constructor
*/
public StopWatch() {
super();
}
/**
* Constructor
*
@ -63,7 +64,13 @@ public class StopWatch {
myStarted = theStart.getTime();
}
public StopWatch(long theL) {
/**
* Constructor
*
* @param theStart The time that the stopwatch was started
*/
public StopWatch(long theStart) {
myStarted = theStart;
}
private void addNewlineIfContentExists(StringBuilder theB) {
@ -120,6 +127,8 @@ public class StopWatch {
b.append(": ");
b.append(formatMillis(delta));
}
} else {
b.append("No tasks");
}
TaskTiming last = null;
@ -257,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();

View File

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

View File

@ -30,7 +30,13 @@ import com.ctc.wstx.stax.WstxOutputFactory;
import org.apache.commons.text.StringEscapeUtils;
import org.codehaus.stax2.XMLOutputFactory2;
import org.codehaus.stax2.io.EscapingWriterFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.*;
import javax.xml.stream.events.XMLEvent;
import java.io.*;
@ -40,7 +46,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
/**
* Utility methods for working with the StAX API.
*
* <p>
* This class contains code adapted from the Apache Axiom project.
*/
public class XmlUtil {
@ -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());
}
};
}
}
}

View File

@ -0,0 +1,48 @@
package ca.uhn.fhir.util.bundle;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
public class BundleEntryMutator {
private final IBase myEntry;
private final BaseRuntimeChildDefinition myRequestChildDef;
private final BaseRuntimeElementCompositeDefinition<?> myRequestChildContentsDef;
public BundleEntryMutator(IBase theEntry, BaseRuntimeChildDefinition theRequestChildDef, BaseRuntimeElementCompositeDefinition<?> theRequestChildContentsDef) {
myEntry = theEntry;
myRequestChildDef = theRequestChildDef;
myRequestChildContentsDef = theRequestChildContentsDef;
}
void setRequestUrl(FhirContext theFhirContext, String theRequestUrl) {
BaseRuntimeChildDefinition requestUrlChildDef = myRequestChildContentsDef.getChildByName("url");
IPrimitiveType<?> url = ParametersUtil.createUri(theFhirContext, theRequestUrl);
for (IBase nextRequest : myRequestChildDef.getAccessor().getValues(myEntry)) {
requestUrlChildDef.getMutator().addValue(nextRequest, url);
}
}
}

View File

@ -0,0 +1,55 @@
package ca.uhn.fhir.util.bundle;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
public class BundleEntryParts {
private final RequestTypeEnum myRequestType;
private final IBaseResource myResource;
private final String myUrl;
private final String myConditionalUrl;
public BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource, String theConditionalUrl) {
super();
myRequestType = theRequestType;
myUrl = theUrl;
myResource = theResource;
myConditionalUrl = theConditionalUrl;
}
public RequestTypeEnum getRequestType() {
return myRequestType;
}
public IBaseResource getResource() {
return myResource;
}
public String getConditionalUrl() {
return myConditionalUrl;
}
public String getUrl() {
return myUrl;
}
}

View File

@ -0,0 +1,38 @@
package ca.uhn.fhir.util.bundle;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class EntryListAccumulator implements Consumer<ModifiableBundleEntry> {
private final List<BundleEntryParts> myList = new ArrayList<>();
@Override
public void accept(ModifiableBundleEntry theModifiableBundleEntry) {
myList.add(theModifiableBundleEntry.getBundleEntryParts());
}
public List<BundleEntryParts> getList() {
return myList;
}
}

View File

@ -0,0 +1,45 @@
package ca.uhn.fhir.util.bundle;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
public class ModifiableBundleEntry {
private final BundleEntryParts myBundleEntryParts;
private final BundleEntryMutator myBundleEntryMutator;
public ModifiableBundleEntry(BundleEntryParts theBundleEntryParts, BundleEntryMutator theBundleEntryMutator) {
myBundleEntryParts = theBundleEntryParts;
myBundleEntryMutator = theBundleEntryMutator;
}
BundleEntryParts getBundleEntryParts() {
return myBundleEntryParts;
}
public void setRequestUrl(FhirContext theFhirContext, String theRequestUrl) {
myBundleEntryMutator.setRequestUrl(theFhirContext, theRequestUrl);
}
public String getRequestUrl() {
return myBundleEntryParts.getUrl();
}
}

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.model.primitive;
package ca.uhn.fhir.validation;
/*
/*-
* #%L
* HAPI FHIR - Core Library
* %%
@ -20,19 +20,13 @@ package ca.uhn.fhir.model.primitive;
* #L%
*/
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
/**
* This interface marks a {@link IValidatorModule validator module} that uses the FHIR
* FhirInstanceValidator as the underlying engine (i.e. it performs
* profile validation)
*/
public interface IInstanceValidatorModule extends IValidatorModule {
@DatatypeDef(name = "idref")
public class IdrefDt extends StringDt {
// nothing extra yet
private IElement myTarget;
public IElement getTarget() {
return myTarget;
}
public void setTarget(IElement theTarget) {
myTarget = theTarget;
}
}

View File

@ -19,26 +19,32 @@ package ca.uhn.fhir.validation;
* limitations under the License.
* #L%
*/
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.*;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXParseException;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class SchemaBaseValidator implements IValidatorModule {
public static final String RESOURCES_JAR_NOTE = "Note that as of HAPI FHIR 1.2, DSTU2 validation files are kept in a separate JAR (hapi-fhir-validation-resources-XXX.jar) which must be added to your classpath. See the HAPI FHIR download page for more information.";
@ -47,7 +53,7 @@ public class SchemaBaseValidator implements IValidatorModule {
private static final Set<String> SCHEMA_NAMES;
static {
HashSet<String> sn = new HashSet<String>();
HashSet<String> sn = new HashSet<>();
sn.add("xml.xsd");
sn.add("xhtml1-strict.xsd");
sn.add("fhir-single.xsd");
@ -59,15 +65,15 @@ public class SchemaBaseValidator implements IValidatorModule {
SCHEMA_NAMES = Collections.unmodifiableSet(sn);
}
private Map<String, Schema> myKeyToSchema = new HashMap<String, Schema>();
private final Map<String, Schema> myKeyToSchema = new HashMap<>();
private FhirContext myCtx;
public SchemaBaseValidator(FhirContext theContext) {
myCtx = theContext;
}
private void doValidate(IValidationContext<?> theContext, String schemaName) {
Schema schema = loadSchema("dstu", schemaName);
private void doValidate(IValidationContext<?> theContext) {
Schema schema = loadSchema();
try {
Validator validator = schema.newValidator();
@ -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);
}
}
}

View File

@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.Collections;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import ca.uhn.fhir.context.FhirContext;
@ -120,7 +121,19 @@ public class ValidationResult {
location = null;
}
String severity = next.getSeverity() != null ? next.getSeverity().getCode() : null;
OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, severity, next.getMessage(), location, Constants.OO_INFOSTATUS_PROCESSING);
IBase issue = OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, severity, next.getMessage(), location, Constants.OO_INFOSTATUS_PROCESSING);
if (next.getLocationLine() != null || next.getLocationCol() != null) {
String line = "(unknown)";
if (next.getLocationLine() != null) {
line = next.getLocationLine().toString();
}
String col = "(unknown)";
if (next.getLocationCol() != null) {
col = next.getLocationCol().toString();
}
OperationOutcomeUtil.addLocationToIssue(myCtx, issue, "Line " + line + ", Col " + col);
}
}
if (myMessages.isEmpty()) {

View File

@ -50,14 +50,12 @@ public interface IAnyResource extends IBaseResource {
String getId();
@Override
IIdType getIdElement();
IPrimitiveType<String> getLanguageElement();
Object getUserData(String name);
@Override
IAnyResource setId(String theId);
void setUserData(String name, Object value);

View File

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

View File

@ -66,7 +66,7 @@ ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceVersionConstraintFail
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceIndexedCompositeStringUniqueConstraintFailure=The operation has failed with a unique index constraint failure. This probably means that the operation was trying to create/update a resource that would have resulted in a duplicate value for a unique index.
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.forcedIdConstraintFailure=The operation has failed with a client-assigned ID constraint failure. This typically means that multiple client threads are trying to create a new resource with the same client-assigned ID at the same time, and this thread was chosen to be rejected.
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.externalizedBinaryStorageExtensionFoundInRequestBody=Illegal extension found in request payload: {0}
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.externalizedBinaryStorageExtensionFoundInRequestBody=Illegal extension found in request payload - URL "{0}" and value "{1}"
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlInvalidResourceType=Invalid match URL "{0}" - Unknown resource type: "{1}"
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlNoMatches=Invalid match URL "{0}" - No resources match this search
@ -125,9 +125,9 @@ ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownPath=Unable to find content
ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownType=Content in resource of type {0} at path {1} is not appropriate for binary storage: {2}
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1}
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.model.api;
import org.junit.Test;
import static org.junit.Assert.*;
public class ResourceMetadataKeyEnumTest {
@Test
public void testHashCode() {
assertEquals(-60968467, ResourceMetadataKeyEnum.PUBLISHED.hashCode());
}
@Test
public void testEquals() {
assertNotEquals(ResourceMetadataKeyEnum.PROFILES, null);
assertNotEquals(ResourceMetadataKeyEnum.PROFILES, "");
assertNotEquals(ResourceMetadataKeyEnum.PROFILES, ResourceMetadataKeyEnum.PUBLISHED);
assertEquals(ResourceMetadataKeyEnum.PROFILES, ResourceMetadataKeyEnum.PROFILES);
}
@Test
public void testExtensionResourceEquals() {
assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://bar"));
assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), null);
assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), "");
assertEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"));
ResourceMetadataKeyEnum.ExtensionResourceMetadataKey foo = new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo");
assertEquals(foo, foo);
}
}

View File

@ -0,0 +1,46 @@
package ca.uhn.fhir.model.api;
import org.junit.Test;
import java.net.URI;
import java.net.URISyntaxException;
import static org.junit.Assert.*;
public class TagTest {
@Test
public void testEquals() {
Tag tag1 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
Tag tag2 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
Tag tag3 = new Tag().setScheme("scheme2").setTerm("term").setLabel("label");
Tag tag4 = new Tag().setScheme("scheme").setTerm("term2").setLabel("label");
assertEquals(tag1, tag1);
assertEquals(tag1, tag2);
assertNotEquals(tag1, tag3);
assertNotEquals(tag1, tag4);
assertNotEquals(tag1, null);
assertNotEquals(tag1, "");
}
@Test
public void testHashCode() {
Tag tag1 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
assertEquals(1920714536, tag1.hashCode());
}
@Test
public void testConstructors() throws URISyntaxException {
assertTrue(new Tag().isEmpty());
assertFalse(new Tag("http://foo").isEmpty());
assertFalse(new Tag("http://foo", "http://bar").isEmpty());
assertFalse(new Tag(new URI("http://foo"), new URI("http://bar"), "Label").isEmpty());
assertTrue(new Tag((URI)null, null, "Label").isEmpty());
assertEquals("http://foo", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getSystem());
assertEquals("http://bar", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getCode());
assertEquals("Label", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getDisplay());
}
}

View File

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

View File

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

View File

@ -2,13 +2,34 @@ package ca.uhn.fhir.rest.param;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
public class TokenParamTest {
@Test
public void testEquals() {
TokenParam tokenParam1 = new TokenParam("foo", "bar");
TokenParam tokenParam2 = new TokenParam("foo", "bar");
TokenParam tokenParam3 = new TokenParam("foo", "baz");
assertEquals(tokenParam1, tokenParam1);
assertEquals(tokenParam1, tokenParam2);
assertNotEquals(tokenParam1, tokenParam3);
assertNotEquals(tokenParam1, null);
assertNotEquals(tokenParam1, "");
}
@Test
public void testHashCode() {
TokenParam tokenParam1 = new TokenParam("foo", "bar");
assertEquals(4716638, tokenParam1.hashCode());
}
@Test
public void testIsEmpty() {
assertFalse(new TokenParam("foo", "bar").isEmpty());
assertTrue(new TokenParam("", "").isEmpty());
assertTrue(new TokenParam().isEmpty());
assertEquals("", new TokenParam().getValueNotNull());
}
}

View File

@ -0,0 +1,62 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.awaitility.Awaitility.await;
public class AsyncUtilTest {
@Test
public void testSleep() {
AsyncUtil.sleep(10);
}
@Test
public void testSleepWithInterrupt() {
AtomicBoolean outcomeHolder = new AtomicBoolean(true);
Thread thread = new Thread(() -> {
boolean outcome = AsyncUtil.sleep(10000);
outcomeHolder.set(outcome);
});
thread.start();
thread.interrupt();
await().until(()-> outcomeHolder.get() == false);
}
@Test
public void testAwaitLatchAndThrowInternalErrorException() {
AtomicBoolean outcomeHolder = new AtomicBoolean(false);
CountDownLatch latch = new CountDownLatch(1);
Thread thread = new Thread(() -> {
try {
AsyncUtil.awaitLatchAndThrowInternalErrorExceptionOnInterrupt(latch, 10, TimeUnit.SECONDS);
} catch (InternalErrorException e) {
outcomeHolder.set(true);
}
});
thread.start();
thread.interrupt();
await().until(()-> outcomeHolder.get());
}
@Test
public void testAwaitLatchIgnoreInterruption() {
AtomicBoolean outcomeHolder = new AtomicBoolean(true);
CountDownLatch latch = new CountDownLatch(1);
Thread thread = new Thread(() -> {
boolean outcome = AsyncUtil.awaitLatchAndIgnoreInterrupt(latch, 10, TimeUnit.SECONDS);
outcomeHolder.set(outcome);
});
thread.start();
thread.interrupt();
await().until(()-> outcomeHolder.get() == false);
}
}

View File

@ -0,0 +1,20 @@
package ca.uhn.fhir.util;
import org.junit.Test;
import static org.junit.Assert.*;
public class FileUtilTest {
@Test
public void formatFileSize() {
assertEquals("0 Bytes", FileUtil.formatFileSize(0).replace(",", "."));
assertEquals("1 Bytes", FileUtil.formatFileSize(1).replace(",", "."));
assertEquals("1.2 kB", FileUtil.formatFileSize(1234).replace(",", "."));
assertEquals("12.1 kB", FileUtil.formatFileSize(12345).replace(",", "."));
assertEquals("11.8 MB", FileUtil.formatFileSize(12345678).replace(",", "."));
assertEquals("103.5 GB", FileUtil.formatFileSize(111111111111L).replace(",", "."));
assertEquals("101.1 TB", FileUtil.formatFileSize(111111111111111L).replace(",", "."));
assertEquals("10105.5 TB", FileUtil.formatFileSize(11111111111111111L).replace(",", "."));
}
}

View File

@ -128,6 +128,56 @@ public class StopWatchTest {
assertEquals("TASK1: 500ms\nTASK2: 100ms", taskDurations);
}
@Test
public void testFormatTaskDurationsDelayBetweenTasks() {
StopWatch sw = new StopWatch();
StopWatch.setNowForUnitTestForUnitTest(1000L);
sw.startTask("TASK1");
StopWatch.setNowForUnitTestForUnitTest(1500L);
sw.endCurrentTask();
StopWatch.setNowForUnitTestForUnitTest(2000L);
sw.startTask("TASK2");
StopWatch.setNowForUnitTestForUnitTest(2100L);
sw.endCurrentTask();
StopWatch.setNowForUnitTestForUnitTest(2200L);
String taskDurations = sw.formatTaskDurations();
ourLog.info(taskDurations);
assertEquals("TASK1: 500ms\n" +
"Between: 500ms\n" +
"TASK2: 100ms\n" +
"After last task: 100ms", taskDurations);
}
@Test
public void testFormatTaskDurationsLongDelayBeforeStart() {
StopWatch sw = new StopWatch(0);
StopWatch.setNowForUnitTestForUnitTest(1000L);
sw.startTask("TASK1");
StopWatch.setNowForUnitTestForUnitTest(1500L);
sw.startTask("TASK2");
StopWatch.setNowForUnitTestForUnitTest(1600L);
String taskDurations = sw.formatTaskDurations();
ourLog.info(taskDurations);
assertEquals("Before first task: 1000ms\nTASK1: 500ms\nTASK2: 100ms", taskDurations);
}
@Test
public void testFormatTaskDurationsNoTasks() {
StopWatch sw = new StopWatch(0);
String taskDurations = sw.formatTaskDurations();
ourLog.info(taskDurations);
assertEquals("No tasks", taskDurations);
}
@Test
public void testFormatThroughput60Ops4Min() {
StopWatch sw = new StopWatch(DateUtils.addMinutes(new Date(), -4));
@ -210,4 +260,34 @@ public class StopWatchTest {
assertThat(string, matchesPattern("^[0-9]{3,4}ms$"));
}
@Test
public void testAppendRightAlignedNumber() {
StringBuilder b= new StringBuilder();
b.setLength(0);
StopWatch.appendRightAlignedNumber(b, "PFX", 0, 100);
assertEquals("PFX100", b.toString());
b.setLength(0);
StopWatch.appendRightAlignedNumber(b, "PFX", 1, 100);
assertEquals("PFX100", b.toString());
b.setLength(0);
StopWatch.appendRightAlignedNumber(b, "PFX", 2, 100);
assertEquals("PFX100", b.toString());
b.setLength(0);
StopWatch.appendRightAlignedNumber(b, "PFX", 3, 100);
assertEquals("PFX100", b.toString());
b.setLength(0);
StopWatch.appendRightAlignedNumber(b, "PFX", 4, 100);
assertEquals("PFX0100", b.toString());
b.setLength(0);
StopWatch.appendRightAlignedNumber(b, "PFX", 10, 100);
assertEquals("PFX0000000100", b.toString());
}
}

View File

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

View File

@ -398,7 +398,7 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
}
protected IGenericClient newClientWithBaseUrl(CommandLine theCommandLine, String theBaseUrl, String theBasicAuthOptionName, String theBearerTokenOptionName) throws ParseException {
myFhirCtx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000);
myFhirCtx.getRestfulClientFactory().setSocketTimeout((int) DateUtils.MILLIS_PER_HOUR);
IGenericClient retVal = myFhirCtx.newRestfulGenericClient(theBaseUrl);
String basicAuthHeaderValue = getAndParseOptionBasicAuthHeader(theCommandLine, theBasicAuthOptionName);

View File

@ -55,6 +55,7 @@ import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@ -79,7 +80,7 @@ public class ExampleDataUploader extends BaseCommand {
}
}
private Bundle getBundleFromFileDstu2(Integer limit, File inputFile, FhirContext ctx) throws IOException, UnsupportedEncodingException {
private Bundle getBundleFromFileDstu2(Integer limit, File inputFile, FhirContext ctx) throws IOException {
Bundle bundle = new Bundle();
@ -98,13 +99,13 @@ public class ExampleDataUploader extends BaseCommand {
break;
}
int len = 0;
int len;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((len = zis.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
byte[] exampleBytes = bos.toByteArray();
String exampleString = new String(exampleBytes, "UTF-8");
String exampleString = new String(exampleBytes, StandardCharsets.UTF_8);
if (ourLog.isTraceEnabled()) {
ourLog.trace("Next example: " + exampleString);
@ -145,7 +146,7 @@ public class ExampleDataUploader extends BaseCommand {
}
@SuppressWarnings("unchecked")
private org.hl7.fhir.dstu3.model.Bundle getBundleFromFileDstu3(Integer limit, File inputFile, FhirContext ctx) throws IOException, UnsupportedEncodingException {
private org.hl7.fhir.dstu3.model.Bundle getBundleFromFileDstu3(Integer limit, File inputFile, FhirContext ctx) throws IOException {
org.hl7.fhir.dstu3.model.Bundle bundle = new org.hl7.fhir.dstu3.model.Bundle();
bundle.setType(BundleType.TRANSACTION);
@ -168,13 +169,13 @@ public class ExampleDataUploader extends BaseCommand {
break;
}
int len = 0;
int len;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((len = zis.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
byte[] exampleBytes = bos.toByteArray();
String exampleString = new String(exampleBytes, "UTF-8");
String exampleString = new String(exampleBytes, StandardCharsets.UTF_8);
if (ourLog.isTraceEnabled()) {
ourLog.trace("Next example: " + exampleString);
@ -229,7 +230,7 @@ public class ExampleDataUploader extends BaseCommand {
}
@SuppressWarnings("unchecked")
private org.hl7.fhir.r4.model.Bundle getBundleFromFileR4(Integer limit, File inputFile, FhirContext ctx) throws IOException, UnsupportedEncodingException {
private org.hl7.fhir.r4.model.Bundle getBundleFromFileR4(Integer limit, File inputFile, FhirContext ctx) throws IOException {
org.hl7.fhir.r4.model.Bundle bundle = new org.hl7.fhir.r4.model.Bundle();
bundle.setType(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION);
@ -252,13 +253,13 @@ public class ExampleDataUploader extends BaseCommand {
break;
}
int len = 0;
int len;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((len = zis.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
byte[] exampleBytes = bos.toByteArray();
String exampleString = new String(exampleBytes, "UTF-8");
String exampleString = new String(exampleBytes, StandardCharsets.UTF_8);
if (ourLog.isTraceEnabled()) {
ourLog.trace("Next example: " + exampleString);
@ -369,8 +370,7 @@ public class ExampleDataUploader extends BaseCommand {
private void processBundleDstu2(FhirContext ctx, Bundle bundle) {
Map<String, Integer> ids = new HashMap<String, Integer>();
Set<String> fullIds = new HashSet<String>();
Set<String> fullIds = new HashSet<>();
for (Iterator<Entry> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
Entry next = iterator.next();
@ -397,13 +397,14 @@ public class ExampleDataUploader extends BaseCommand {
}
}
}
Set<String> qualIds = new TreeSet<String>();
for (Iterator<Entry> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
Entry next = iterator.next();
Set<String> qualIds = new TreeSet<>();
for (Entry next : bundle.getEntry()) {
if (next.getResource().getId().getIdPart() != null) {
String nextId = next.getResource().getId().getValue();
next.getRequest().setMethod(HTTPVerbEnum.PUT);
next.getRequest().setUrl(nextId);
qualIds.add(nextId);
}
}
@ -449,15 +450,14 @@ public class ExampleDataUploader extends BaseCommand {
private void processBundleDstu3(FhirContext ctx, org.hl7.fhir.dstu3.model.Bundle bundle) {
Map<String, Integer> ids = new HashMap<String, Integer>();
Set<String> fullIds = new HashSet<String>();
Set<String> fullIds = new HashSet<>();
for (Iterator<BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
BundleEntryComponent next = iterator.next();
// DataElement have giant IDs that seem invalid, need to investigate this..
if ("Subscription".equals(next.getResource().getResourceType()) || "DataElement".equals(next.getResource().getResourceType())
|| "OperationOutcome".equals(next.getResource().getResourceType()) || "OperationDefinition".equals(next.getResource().getResourceType())) {
if ("Subscription".equals(next.getResource().getResourceType().name()) || "DataElement".equals(next.getResource().getResourceType().name())
|| "OperationOutcome".equals(next.getResource().getResourceType().name()) || "OperationDefinition".equals(next.getResource().getResourceType().name())) {
ourLog.info("Skipping " + next.getResource().getResourceType() + " example");
iterator.remove();
} else {
@ -477,13 +477,13 @@ public class ExampleDataUploader extends BaseCommand {
}
}
}
Set<String> qualIds = new TreeSet<String>();
for (Iterator<BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
BundleEntryComponent next = iterator.next();
Set<String> qualIds = new TreeSet<>();
for (BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource().getIdElement().getIdPart() != null) {
String nextId = next.getResource().getIdElement().getValue();
next.getRequest().setMethod(HTTPVerb.PUT);
next.getRequest().setUrl(nextId);
qualIds.add(nextId);
}
}
@ -529,15 +529,14 @@ public class ExampleDataUploader extends BaseCommand {
private void processBundleR4(FhirContext ctx, org.hl7.fhir.r4.model.Bundle bundle) {
Map<String, Integer> ids = new HashMap<String, Integer>();
Set<String> fullIds = new HashSet<String>();
Set<String> fullIds = new HashSet<>();
for (Iterator<org.hl7.fhir.r4.model.Bundle.BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next = iterator.next();
// DataElement have giant IDs that seem invalid, need to investigate this..
if ("Subscription".equals(next.getResource().getResourceType()) || "DataElement".equals(next.getResource().getResourceType())
|| "OperationOutcome".equals(next.getResource().getResourceType()) || "OperationDefinition".equals(next.getResource().getResourceType())) {
if ("Subscription".equals(next.getResource().getResourceType().name()) || "DataElement".equals(next.getResource().getResourceType().name())
|| "OperationOutcome".equals(next.getResource().getResourceType().name()) || "OperationDefinition".equals(next.getResource().getResourceType().name())) {
ourLog.info("Skipping " + next.getResource().getResourceType() + " example");
iterator.remove();
} else {
@ -557,13 +556,13 @@ public class ExampleDataUploader extends BaseCommand {
}
}
}
Set<String> qualIds = new TreeSet<String>();
for (Iterator<org.hl7.fhir.r4.model.Bundle.BundleEntryComponent> iterator = bundle.getEntry().iterator(); iterator.hasNext(); ) {
org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next = iterator.next();
Set<String> qualIds = new TreeSet<>();
for (org.hl7.fhir.r4.model.Bundle.BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource().getIdElement().getIdPart() != null) {
String nextId = next.getResource().getIdElement().getValue();
next.getRequest().setMethod(org.hl7.fhir.r4.model.Bundle.HTTPVerb.PUT);
next.getRequest().setUrl(nextId);
qualIds.add(nextId);
}
}
@ -635,7 +634,7 @@ public class ExampleDataUploader extends BaseCommand {
boolean cacheFile = theCommandLine.hasOption('c');
Collection<File> inputFiles = null;
Collection<File> inputFiles;
try {
inputFiles = loadFile(specUrl, filepath, cacheFile);
for (File inputFile : inputFiles) {
@ -694,13 +693,11 @@ public class ExampleDataUploader extends BaseCommand {
continue;
}
boolean found = false;
for (int j = 0; j < resources.size(); j++) {
String candidateTarget = resources.get(j).getIdElement().getValue();
if (isNotBlank(nextTarget) && nextTarget.equals(candidateTarget)) {
ourLog.info("Reflexively adding resource {} to bundle as it is a reference target", nextTarget);
subResourceList.add(resources.remove(j));
found = true;
break;
}
}

View File

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

View File

@ -91,7 +91,7 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -98,7 +98,7 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) {
return null;
}

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.cli;
* #L%
*/
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
@ -44,7 +44,7 @@ public class ToggleSearchParametersCommand extends BaseCommand {
Options options = new Options();
addFhirVersionOption(options);
addBaseUrlOption(options);
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.SCT_URI + ")");
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + ITermLoaderSvc.SCT_URI + ")");
addBasicAuthOption(options);
return options;
}

View File

@ -20,30 +20,48 @@ package ca.uhn.fhir.cli;
* #L%
*/
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.util.AttachmentUtil;
import ca.uhn.fhir.util.FileUtil;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CountingInputStream;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.r4.model.CodeSystem;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class UploadTerminologyCommand extends BaseCommand {
// TODO: Don't use qualified names for loggers in HAPI CLI.
static final String UPLOAD_TERMINOLOGY = "upload-terminology";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UploadTerminologyCommand.class);
private static final String UPLOAD_EXTERNAL_CODE_SYSTEM = "upload-external-code-system";
private static final long DEFAULT_TRANSFER_SIZE_LIMIT = 10 * FileUtils.ONE_MB;
private static long ourTransferSizeLimit = DEFAULT_TRANSFER_SIZE_LIMIT;
@Override
public String getCommandDescription() {
return "Uploads a terminology package (e.g. a SNOMED CT ZIP file) to a server, using the $" + UPLOAD_EXTERNAL_CODE_SYSTEM + " operation.";
return "Uploads a terminology package (e.g. a SNOMED CT ZIP file or a custom terminology bundle) to a server, using the appropriate operation.";
}
@Override
public String getCommandName() {
return "upload-terminology";
return UPLOAD_TERMINOLOGY;
}
@Override
@ -52,9 +70,9 @@ public class UploadTerminologyCommand extends BaseCommand {
addFhirVersionOption(options);
addBaseUrlOption(options);
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.SCT_URI + ")");
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + ITermLoaderSvc.SCT_URI + ")");
addOptionalOption(options, "d", "data", true, "Local file to use to upload (can be a raw file or a ZIP containing the raw file)");
addOptionalOption(options, null, "custom", false, "Indicates that this upload uses the HAPI FHIR custom external terminology format");
addOptionalOption(options, "m", "mode", true, "The upload mode: SNAPSHOT (default), ADD, REMOVE");
addBasicAuthOption(options);
addVerboseLoggingOption(options);
@ -65,6 +83,14 @@ public class UploadTerminologyCommand extends BaseCommand {
public void run(CommandLine theCommandLine) throws ParseException {
parseFhirContext(theCommandLine);
ModeEnum mode;
String modeString = theCommandLine.getOptionValue("m", "SNAPSHOT");
try {
mode = ModeEnum.valueOf(modeString);
} catch (IllegalArgumentException e) {
throw new ParseException("Invalid mode: " + modeString);
}
String termUrl = theCommandLine.getOptionValue("u");
if (isBlank(termUrl)) {
throw new ParseException("No URL provided");
@ -77,29 +103,166 @@ public class UploadTerminologyCommand extends BaseCommand {
IGenericClient client = super.newClient(theCommandLine);
IBaseParameters inputParameters = ParametersUtil.newInstance(myFhirCtx);
ParametersUtil.addParameterToParametersUri(myFhirCtx, inputParameters, "url", termUrl);
for (String next : datafile) {
ParametersUtil.addParameterToParametersString(myFhirCtx, inputParameters, "localfile", next);
}
if (theCommandLine.hasOption("custom")) {
ParametersUtil.addParameterToParametersCode(myFhirCtx, inputParameters, "contentMode", "custom");
}
if (theCommandLine.hasOption(VERBOSE_LOGGING_PARAM)) {
client.registerInterceptor(new LoggingInterceptor(true));
}
switch (mode) {
case SNAPSHOT:
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM);
break;
case ADD:
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD);
break;
case REMOVE:
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE);
break;
}
}
private void invokeOperation(String theTermUrl, String[] theDatafile, IGenericClient theClient, IBaseParameters theInputParameters, String theOperationName) throws ParseException {
ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_SYSTEM, theTermUrl);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8);
int compressedSourceBytesCount = 0;
int compressedFileCount = 0;
boolean haveCompressedContents = false;
try {
for (String nextDataFile : theDatafile) {
try (FileInputStream fileInputStream = new FileInputStream(nextDataFile)) {
if (nextDataFile.endsWith(".csv")) {
ourLog.info("Compressing and adding file: {}", nextDataFile);
ZipEntry nextEntry = new ZipEntry(stripPath(nextDataFile));
zipOutputStream.putNextEntry(nextEntry);
CountingInputStream countingInputStream = new CountingInputStream(fileInputStream);
IOUtils.copy(countingInputStream, zipOutputStream);
haveCompressedContents = true;
compressedSourceBytesCount += countingInputStream.getCount();
zipOutputStream.flush();
ourLog.info("Finished compressing {}", nextDataFile);
} else if (nextDataFile.endsWith(".zip")) {
ourLog.info("Adding ZIP file: {}", nextDataFile);
String fileName = "file:" + nextDataFile;
addFileToRequestBundle(theInputParameters, fileName, IOUtils.toByteArray(fileInputStream));
} else if (nextDataFile.endsWith(".json") || nextDataFile.endsWith(".xml")) {
ourLog.info("Adding CodeSystem resource file: {}", nextDataFile);
String contents = IOUtils.toString(fileInputStream, Charsets.UTF_8);
EncodingEnum encoding = EncodingEnum.detectEncodingNoDefault(contents);
if (encoding == null) {
throw new ParseException("Could not detect FHIR encoding for file: " + nextDataFile);
}
CodeSystem resource = encoding.newParser(myFhirCtx).parseResource(CodeSystem.class, contents);
ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_CODESYSTEM, resource);
} else {
throw new ParseException("Don't know how to handle file: " + nextDataFile);
}
}
}
zipOutputStream.flush();
zipOutputStream.close();
} catch (IOException e) {
throw new ParseException(e.toString());
}
if (haveCompressedContents) {
byte[] compressedBytes = byteArrayOutputStream.toByteArray();
ourLog.info("Compressed {} bytes in {} file(s) into {} bytes", FileUtil.formatFileSize(compressedSourceBytesCount), compressedFileCount, FileUtil.formatFileSize(compressedBytes.length));
addFileToRequestBundle(theInputParameters, "file:/files.zip", compressedBytes);
}
ourLog.info("Beginning upload - This may take a while...");
IBaseParameters response = client
.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;
}
}

View File

@ -34,7 +34,13 @@
<!--
It's useful to have this log when uploading big terminologies
-->
<logger name="ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl" additivity="false" level="info">
<logger name="ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.LoggingRule;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
@ -24,10 +25,9 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals;
import ca.uhn.fhir.test.utilities.JettyUtil;
public class ExportConceptMapToCsvCommandDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandDstu3Test.class);
private static final String CM_URL = "http://example.com/conceptmap";
@ -36,7 +36,7 @@ public class ExportConceptMapToCsvCommandDstu3Test {
private static final String CS_URL_1 = "http://example.com/codesystem/1";
private static final String CS_URL_2 = "http://example.com/codesystem/2";
private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILE = "./target/output.csv";
private static final String FILE = "./target/output_dstu3.csv";
private static String ourBase;
private static IGenericClient ourClient;
@ -44,13 +44,47 @@ public class ExportConceptMapToCsvCommandDstu3Test {
private static int ourPort;
private static Server ourServer;
private static String ourVersion = "dstu3";
@Rule
public LoggingRule myLoggingRule = new LoggingRule();
static {
System.setProperty("test", "true");
}
@Rule
public LoggingRule myLoggingRule = new LoggingRule();
@Test
public void testExportConceptMapToCsvCommand() throws IOException {
ourLog.debug("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
App.main(new String[]{"export-conceptmap-to-csv",
"-v", ourVersion,
"-t", ourBase,
"-u", CM_URL,
"-f", FILE,
"-l"});
await().until(() -> new File(FILE).exists());
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
ourLog.info("Going to read file: {}", FILE);
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
assertEquals(expected, result);
FileUtils.deleteQuietly(new File(FILE));
}
@AfterClass
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
@ -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

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.FileUtils;
@ -22,10 +23,9 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals;
import ca.uhn.fhir.test.utilities.JettyUtil;
public class ExportConceptMapToCsvCommandR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandR4Test.class);
private static final String CM_URL = "http://example.com/conceptmap";
@ -34,7 +34,7 @@ public class ExportConceptMapToCsvCommandR4Test {
private static final String CS_URL_1 = "http://example.com/codesystem/1";
private static final String CS_URL_2 = "http://example.com/codesystem/2";
private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILE = new File("./target/output.csv").getAbsolutePath();
private static final String FILE = new File("./target/output_r4.csv").getAbsolutePath();
private static String ourBase;
private static IGenericClient ourClient;
@ -47,6 +47,37 @@ public class ExportConceptMapToCsvCommandR4Test {
System.setProperty("test", "true");
}
@Test
public void testExportConceptMapToCsvCommand() throws IOException {
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
App.main(new String[]{"export-conceptmap-to-csv",
"-v", ourVersion,
"-t", ourBase,
"-u", CM_URL,
"-f", FILE,
"-l"});
await().until(() -> new File(FILE).exists());
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
assertEquals(expected, result);
FileUtils.deleteQuietly(new File(FILE));
}
@AfterClass
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
@ -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

View File

@ -0,0 +1,367 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.term.UploadStatistics;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.test.utilities.JettyUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hamcrest.Matchers;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.*;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class UploadTerminologyCommandTest extends BaseTest {
static {
System.setProperty("test", "true");
}
private Server myServer;
private FhirContext myCtx = FhirContext.forR4();
@Mock
private ITermLoaderSvc myTermLoaderSvc;
@Captor
private ArgumentCaptor<List<ITermLoaderSvc.FileDescriptor>> myDescriptorListCaptor;
private int myPort;
private String myConceptsFileName = "target/concepts.csv";
private File myConceptsFile = new File(myConceptsFileName);
private String myHierarchyFileName = "target/hierarchy.csv";
private File myHierarchyFile = new File(myHierarchyFileName);
private String myCodeSystemFileName = "target/codesystem.json";
private File myCodeSystemFile = new File(myCodeSystemFileName);
private String myTextFileName = "target/hello.txt";
private File myTextFile = new File(myTextFileName);
private File myArchiveFile;
private String myArchiveFileName;
@Test
public void testDeltaAdd() throws IOException {
writeConceptAndHierarchyFiles();
when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myConceptsFileName,
"-d", myHierarchyFileName
});
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any());
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
assertEquals(1, listOfDescriptors.size());
assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename());
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
}
@Test
public void testDeltaAddUsingCodeSystemResource() throws IOException {
try (FileWriter w = new FileWriter(myCodeSystemFile, false)) {
CodeSystem cs = new CodeSystem();
cs.addConcept().setCode("CODE").setDisplay("Display");
myCtx.newJsonParser().encodeResourceToWriter(cs, w);
}
when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myCodeSystemFileName
});
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any());
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
assertEquals(1, listOfDescriptors.size());
assertEquals("concepts.csv", listOfDescriptors.get(0).getFilename());
String uploadFile = IOUtils.toString(listOfDescriptors.get(0).getInputStream(), Charsets.UTF_8);
assertThat(uploadFile, containsString("CODE,Display"));
}
@Test
public void testDeltaAddInvalidResource() throws IOException {
try (FileWriter w = new FileWriter(myCodeSystemFile, false)) {
Patient patient = new Patient();
patient.setActive(true);
myCtx.newJsonParser().encodeResourceToWriter(patient, w);
}
try {
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myCodeSystemFileName
});
fail();
} catch (Error e) {
assertThat(e.toString(), containsString("Incorrect resource type found, expected \"CodeSystem\" but found \"Patient\""));
}
}
@Test
public void testDeltaAddInvalidFileType() throws IOException {
try (FileWriter w = new FileWriter(myTextFileName, false)) {
w.append("Help I'm a Bug");
}
try {
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myTextFileName
});
fail();
} catch (Error e) {
assertThat(e.toString(), containsString("Don't know how to handle file:"));
}
}
@Test
public void testDeltaAddUsingCompressedFile() throws IOException {
writeConceptAndHierarchyFiles();
writeArchiveFile(myConceptsFile, myHierarchyFile);
when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myArchiveFileName
});
verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any());
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
assertEquals(1, listOfDescriptors.size());
assertThat(listOfDescriptors.get(0).getFilename(), matchesPattern("^file:.*temp.*\\.zip$"));
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
}
@Test
public void testDeltaAddInvalidFileName() throws IOException {
writeConceptAndHierarchyFiles();
try {
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "ADD",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myConceptsFileName + "/foo.csv",
"-d", myHierarchyFileName
});
} catch (Error e) {
assertThat(e.toString(), Matchers.containsString("FileNotFoundException: target/concepts.csv/foo.csv"));
}
}
@Test
public void testDeltaRemove() throws IOException {
writeConceptAndHierarchyFiles();
when(myTermLoaderSvc.loadDeltaRemove(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "REMOVE",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myConceptsFileName,
"-d", myHierarchyFileName
});
verify(myTermLoaderSvc, times(1)).loadDeltaRemove(eq("http://foo"), myDescriptorListCaptor.capture(), any());
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
assertEquals(1, listOfDescriptors.size());
assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename());
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
}
@Test
public void testSnapshot() throws IOException {
writeConceptAndHierarchyFiles();
when(myTermLoaderSvc.loadCustom(any(), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "SNAPSHOT",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myConceptsFileName,
"-d", myHierarchyFileName
});
verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any());
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
assertEquals(1, listOfDescriptors.size());
assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename());
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
}
/**
* When transferring large files, we use a local file to store the binary instead of
* using HTTP to transfer a giant base 64 encoded attachment. Hopefully we can
* replace this with a bulk data import at some point when that gets implemented.
*/
@Test
public void testSnapshotLargeFile() throws IOException {
UploadTerminologyCommand.setTransferSizeLimitForUnitTest(10);
writeConceptAndHierarchyFiles();
when(myTermLoaderSvc.loadCustom(any(), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "SNAPSHOT",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myConceptsFileName,
"-d", myHierarchyFileName
});
verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any());
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
assertEquals(1, listOfDescriptors.size());
assertThat(listOfDescriptors.get(0).getFilename(), matchesPattern(".*\\.zip$"));
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
}
private void writeArchiveFile(File... theFiles) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8);
for (File next : theFiles) {
ZipEntry nextEntry = new ZipEntry(UploadTerminologyCommand.stripPath(next.getAbsolutePath()));
zipOutputStream.putNextEntry(nextEntry);
try (FileInputStream fileInputStream = new FileInputStream(next)) {
IOUtils.copy(fileInputStream, zipOutputStream);
}
}
zipOutputStream.flush();
zipOutputStream.close();
myArchiveFile = File.createTempFile("temp", ".zip");
myArchiveFile.deleteOnExit();
myArchiveFileName = myArchiveFile.getAbsolutePath();
try (FileOutputStream fos = new FileOutputStream(myArchiveFile, false)) {
fos.write(byteArrayOutputStream.toByteArray());
}
}
private void writeConceptAndHierarchyFiles() throws IOException {
try (FileWriter w = new FileWriter(myConceptsFile, false)) {
w.append("CODE,DISPLAY\n");
w.append("ANIMALS,Animals\n");
w.append("CATS,Cats\n");
w.append("DOGS,Dogs\n");
}
try (FileWriter w = new FileWriter(myHierarchyFile, false)) {
w.append("PARENT,CHILD\n");
w.append("ANIMALS,CATS\n");
w.append("ANIMALS,DOGS\n");
}
}
@After
public void after() throws Exception {
JettyUtil.closeServer(myServer);
FileUtils.deleteQuietly(myConceptsFile);
FileUtils.deleteQuietly(myHierarchyFile);
FileUtils.deleteQuietly(myArchiveFile);
FileUtils.deleteQuietly(myCodeSystemFile);
FileUtils.deleteQuietly(myTextFile);
UploadTerminologyCommand.setTransferSizeLimitForUnitTest(-1);
}
@Before
public void before() throws Exception {
myServer = new Server(0);
TerminologyUploaderProvider provider = new TerminologyUploaderProvider(myCtx, myTermLoaderSvc);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(myCtx);
servlet.registerProvider(provider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
myServer.setHandler(proxyHandler);
JettyUtil.startServer(myServer);
myPort = JettyUtil.getPortForStartedServer(myServer);
}
}

View File

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

View File

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

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.client.apache;
*/
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import ca.uhn.fhir.rest.client.impl.BaseHttpResponse;
@ -53,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());
}

View File

@ -71,18 +71,18 @@ public class ApacheRestfulClientFactory extends RestfulClientFactory {
}
@Override
protected ApacheHttpClient getHttpClient(String theServerBase) {
protected synchronized ApacheHttpClient getHttpClient(String theServerBase) {
return new ApacheHttpClient(getNativeHttpClient(), new StringBuilder(theServerBase), null, null, null, null);
}
@Override
public IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams,
public synchronized IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams,
String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) {
return new ApacheHttpClient(getNativeHttpClient(), theUrl, theIfNoneExistParams, theIfNoneExistString, theRequestType,
theHeaders);
}
public synchronized HttpClient getNativeHttpClient() {
public HttpClient getNativeHttpClient() {
if (myHttpClient == null) {
//FIXME potential resoource leak

View File

@ -71,52 +71,52 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
}
@Override
public int getConnectionRequestTimeout() {
public synchronized int getConnectionRequestTimeout() {
return myConnectionRequestTimeout;
}
@Override
public int getConnectTimeout() {
public synchronized int getConnectTimeout() {
return myConnectTimeout;
}
/**
* Return the proxy username to authenticate with the HTTP proxy
*/
protected String getProxyUsername() {
protected synchronized String getProxyUsername() {
return myProxyUsername;
}
/**
* Return the proxy password to authenticate with the HTTP proxy
*/
protected String getProxyPassword() {
protected synchronized String getProxyPassword() {
return myProxyPassword;
}
@Override
public void setProxyCredentials(String theUsername, String thePassword) {
public synchronized void setProxyCredentials(String theUsername, String thePassword) {
myProxyUsername = theUsername;
myProxyPassword = thePassword;
}
@Override
public ServerValidationModeEnum getServerValidationMode() {
public synchronized ServerValidationModeEnum getServerValidationMode() {
return myServerValidationMode;
}
@Override
public int getSocketTimeout() {
public synchronized int getSocketTimeout() {
return mySocketTimeout;
}
@Override
public int getPoolMaxTotal() {
public synchronized int getPoolMaxTotal() {
return myPoolMaxTotal;
}
@Override
public int getPoolMaxPerRoute() {
public synchronized int getPoolMaxPerRoute() {
return myPoolMaxPerRoute;
}
@ -217,7 +217,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
}
@Override
public void setServerValidationMode(ServerValidationModeEnum theServerValidationMode) {
public synchronized void setServerValidationMode(ServerValidationModeEnum theServerValidationMode) {
Validate.notNull(theServerValidationMode, "theServerValidationMode may not be null");
myServerValidationMode = theServerValidationMode;
}
@ -242,13 +242,13 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
@Deprecated // override deprecated method
@Override
public ServerValidationModeEnum getServerValidationModeEnum() {
public synchronized ServerValidationModeEnum getServerValidationModeEnum() {
return getServerValidationMode();
}
@Deprecated // override deprecated method
@Override
public void setServerValidationModeEnum(ServerValidationModeEnum theServerValidationMode) {
public synchronized void setServerValidationModeEnum(ServerValidationModeEnum theServerValidationMode) {
setServerValidationMode(theServerValidationMode);
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.client.interceptor;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -76,6 +77,7 @@ public class LoggingInterceptor implements IClientInterceptor {
}
}
@Override
@Hook(Pointcut.CLIENT_REQUEST)
public void interceptRequest(IHttpRequest theRequest) {
if (myLogRequestSummary) {
@ -93,14 +95,13 @@ public class LoggingInterceptor implements IClientInterceptor {
if (content != null) {
myLog.info("Client request body:\n{}", content);
}
} catch (IllegalStateException e) {
myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
} catch (IOException e) {
} catch (IllegalStateException | IOException e) {
myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
}
}
}
@Override
@Hook(Pointcut.CLIENT_RESPONSE)
public void interceptResponse(IHttpResponse theResponse) throws IOException {
if (myLogResponseSummary) {
@ -145,11 +146,8 @@ public class LoggingInterceptor implements IClientInterceptor {
}
if (myLogResponseBody) {
//TODO: Use of a deprecated method should be resolved.
theResponse.bufferEntitity();
InputStream respEntity = null;
try {
respEntity = theResponse.readEntity();
theResponse.bufferEntity();
try (InputStream respEntity = theResponse.readEntity()) {
if (respEntity != null) {
final byte[] bytes;
try {
@ -157,12 +155,10 @@ public class LoggingInterceptor implements IClientInterceptor {
} catch (IllegalStateException e) {
throw new InternalErrorException(e);
}
myLog.info("Client response body:\n{}", new String(bytes, "UTF-8"));
myLog.info("Client response body:\n{}", new String(bytes, StandardCharsets.UTF_8));
} else {
myLog.info("Client response body: (none)");
}
} finally {
IOUtils.closeQuietly(respEntity);
}
}
}
@ -176,7 +172,9 @@ public class LoggingInterceptor implements IClientInterceptor {
Iterator<String> values = theHeaders.get(key).iterator();
while(values.hasNext()) {
String value = values.next();
b.append(key + ": " + value);
b.append(key);
b.append(": ");
b.append(value);
if (nameEntries.hasNext() || values.hasNext()) {
b.append('\n');
}

View File

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

View File

@ -10,8 +10,7 @@
</parent>
<artifactId>hapi-fhir-converter</artifactId>
<!-- TODO: BND changed to jar temporarily -->
<packaging>jar</packaging>
<packaging>bundle</packaging>
<dependencies>
<dependency>
@ -152,10 +151,6 @@
</resource>
</resources>
<plugins>
<!--
TODO: BND
With this enabled, JAR file contains a bunch of .class files that
seem to get copied in from org.hl7.fhir.convertors
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@ -170,7 +165,6 @@
</instructions>
</configuration>
</plugin>
-->
</plugins>
</build>
</project>

View File

@ -29,6 +29,8 @@ import ca.uhn.fhir.rest.api.server.ResponseDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
import org.hl7.fhir.converter.NullVersionConverterAdvisor30;
import org.hl7.fhir.converter.NullVersionConverterAdvisor40;
import org.hl7.fhir.convertors.*;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.exceptions.FHIRException;

View File

@ -1,4 +1,4 @@
package org.hl7.fhir.convertors;
package org.hl7.fhir.converter;
/*
* #%L
@ -20,6 +20,7 @@ package org.hl7.fhir.convertors;
* #L%
*/
import org.hl7.fhir.convertors.VersionConvertorAdvisor30;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.ValueSet;

View File

@ -1,4 +1,4 @@
package org.hl7.fhir.convertors;
package org.hl7.fhir.converter;
/*
* #%L
@ -20,6 +20,7 @@ package org.hl7.fhir.convertors;
* #L%
*/
import org.hl7.fhir.convertors.VersionConvertorAdvisor40;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.dstu2.model.Resource;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;

View File

@ -1,4 +1,4 @@
package org.hl7.fhir.convertors;
package org.hl7.fhir.converter;
/*
* #%L
@ -20,6 +20,7 @@ package org.hl7.fhir.convertors;
* #L%
*/
import org.hl7.fhir.convertors.VersionConvertorAdvisor50;
import org.hl7.fhir.dstu2.model.Resource;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.Bundle;

View File

@ -1,7 +1,8 @@
package org.hl7.fhir.convertors;
package org.hl7.fhir.converter;
import static org.junit.Assert.assertEquals;
import org.hl7.fhir.convertors.VersionConvertor_10_30;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.dstu2.model.Resource;

View File

@ -1,7 +1,8 @@
package org.hl7.fhir.convertors;
package org.hl7.fhir.converter;
import static org.junit.Assert.assertEquals;
import org.hl7.fhir.convertors.VersionConvertor_14_30;
import org.hl7.fhir.exceptions.FHIRException;
import org.junit.Test;

View File

@ -87,6 +87,12 @@
<artifactId>slf4j-simple</artifactId>
<version>1.7.28</version>
</dependency>
<!-- Needed for JEE/Servlet support -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View File

@ -20,7 +20,7 @@ package ca.uhn.hapi.fhir.docs;
* #L%
*/
import org.hl7.fhir.convertors.NullVersionConverterAdvisor30;
import org.hl7.fhir.converter.NullVersionConverterAdvisor30;
import org.hl7.fhir.convertors.VersionConvertor_10_30;
import org.hl7.fhir.convertors.VersionConvertor_14_30;
import org.hl7.fhir.exceptions.FHIRException;

View File

@ -290,7 +290,7 @@ public class ValidatorExamples {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
// TODO: implement (or return null if your implementation does not support this function)
return null;
}

View File

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

View File

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

View File

@ -123,7 +123,7 @@ public class IgPackValidationSupportDstu3 implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -51,11 +51,6 @@ public class JaxRsHttpResponse extends BaseHttpResponse implements IHttpResponse
this.myResponse = theResponse;
}
@Override
public void bufferEntitity() throws IOException {
bufferEntity();
}
@Override
public void bufferEntity() throws IOException {
if(!myBufferedEntity && myResponse.hasEntity()) {

View File

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

View File

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

View File

@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.binstore;
* #L%
*/
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PayloadTooLargeException;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
@ -34,6 +33,8 @@ import javax.annotation.Nonnull;
import java.io.InputStream;
import java.security.SecureRandom;
import static org.apache.commons.lang3.StringUtils.isBlank;
abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
private final SecureRandom myRandom;
private final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@ -66,7 +67,8 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
myMinimumBinarySize = theMinimumBinarySize;
}
String newRandomId() {
@Override
public String newBlobId() {
StringBuilder b = new StringBuilder();
for (int i = 0; i < ID_LENGTH; i++) {
int nextInt = Math.abs(myRandom.nextInt());
@ -89,13 +91,13 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
@Nonnull
CountingInputStream createCountingInputStream(InputStream theInputStream) {
InputStream is = ByteStreams.limit(theInputStream, myMaximumBinarySize + 1L);
InputStream is = ByteStreams.limit(theInputStream, getMaximumBinarySize() + 1L);
return new CountingInputStream(is) {
@Override
public int getCount() {
int retVal = super.getCount();
if (retVal > myMaximumBinarySize) {
throw new PayloadTooLargeException("Binary size exceeds maximum: " + myMaximumBinarySize);
if (retVal > getMaximumBinarySize()) {
throw new PayloadTooLargeException("Binary size exceeds maximum: " + getMaximumBinarySize());
}
return retVal;
}
@ -103,4 +105,11 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
}
String provideIdForNewBlob(String theBlobIdOrNull) {
String id = theBlobIdOrNull;
if (isBlank(theBlobIdOrNull)) {
id = newBlobId();
}
return id;
}
}

View File

@ -86,20 +86,13 @@ public class BinaryAccessProvider {
IBinaryTarget target = findAttachmentForRequest(resource, path, theRequestDetails);
Optional<? extends IBaseExtension<?, ?>> attachmentId = target
.getTarget()
.getExtension()
.stream()
.filter(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
.findFirst();
Optional<String> attachmentId = target.getAttachmentId();
if (attachmentId.isPresent()) {
@SuppressWarnings("unchecked")
IPrimitiveType<String> value = (IPrimitiveType<String>) attachmentId.get().getValue();
String blobId = value.getValueAsString();
String blobId = attachmentId.get();
IBinaryStorageSvc.StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(theResourceId, blobId);
StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(theResourceId, blobId);
if (blobDetails == null) {
String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "unknownBlobId");
throw new InvalidRequestException(msg);
@ -179,7 +172,7 @@ public class BinaryAccessProvider {
if (size > 0) {
if (myBinaryStorageSvc != null) {
if (myBinaryStorageSvc.shouldStoreBlob(size, theResourceId, requestContentType)) {
IBinaryStorageSvc.StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(theResourceId, requestContentType, theRequestDetails.getInputStream());
StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(theResourceId, null, requestContentType, theRequestDetails.getInputStream());
size = storedDetails.getBytes();
blobId = storedDetails.getBlobId();
Validate.notBlank(blobId, "BinaryStorageSvc returned a null blob ID"); // should not happen
@ -192,19 +185,7 @@ public class BinaryAccessProvider {
size = bytes.length;
target.setData(bytes);
} else {
target
.getTarget()
.getExtension()
.removeIf(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()));
target.setData(null);
IBaseExtension<?, ?> ext = target.getTarget().addExtension();
ext.setUrl(JpaConstants.EXT_EXTERNALIZED_BINARY_ID);
ext.setUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED, Boolean.TRUE);
IPrimitiveType<String> blobIdString = (IPrimitiveType<String>) myCtx.getElementDefinition("string").newInstance();
blobIdString.setValueAsString(blobId);
ext.setValue(blobIdString);
replaceDataWithExtension(target, blobId);
}
target.setContentType(requestContentType);
@ -217,52 +198,81 @@ public class BinaryAccessProvider {
return outcome.getResource();
}
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();
}
}

View File

@ -20,23 +20,37 @@ package ca.uhn.fhir.jpa.binstore;
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.IModelVisitor2;
import org.apache.commons.io.FileUtils;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.IdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.EXT_EXTERNALIZED_BINARY_ID;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Interceptor
public class BinaryStorageInterceptor {
@ -45,12 +59,39 @@ public class BinaryStorageInterceptor {
private IBinaryStorageSvc myBinaryStorageSvc;
@Autowired
private FhirContext myCtx;
@Autowired
private BinaryAccessProvider myBinaryAccessProvider;
private Class<? extends IPrimitiveType<byte[]>> myBinaryType;
private String myDeferredListKey;
private long myAutoDeExternalizeMaximumBytes = 10 * FileUtils.ONE_MB;
/**
* Any externalized binaries will be rehydrated if their size is below this thhreshold when
* reading the resource back. Default is 10MB.
*/
public long getAutoDeExternalizeMaximumBytes() {
return myAutoDeExternalizeMaximumBytes;
}
/**
* Any externalized binaries will be rehydrated if their size is below this thhreshold when
* reading the resource back. Default is 10MB.
*/
public void setAutoDeExternalizeMaximumBytes(long theAutoDeExternalizeMaximumBytes) {
myAutoDeExternalizeMaximumBytes = theAutoDeExternalizeMaximumBytes;
}
@SuppressWarnings("unchecked")
@PostConstruct
public void start() {
myBinaryType = (Class<? extends IPrimitiveType<byte[]>>) myCtx.getElementDefinition("base64Binary").getImplementingClass();
myDeferredListKey = getClass().getName() + "_" + hashCode() + "_DEFERRED_LIST";
}
@Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE)
public void expungeResource(AtomicInteger theCounter, IBaseResource theResource) {
Class<? extends IBase> binaryType = myCtx.getElementDefinition("base64Binary").getImplementingClass();
List<? extends IBase> binaryElements = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, binaryType);
List<? extends IBase> binaryElements = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, myBinaryType);
List<String> attachmentIds = binaryElements
.stream()
@ -67,4 +108,215 @@ public class BinaryStorageInterceptor {
}
}
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
public void extractLargeBinariesBeforeCreate(ServletRequestDetails theRequestDetails, IBaseResource theResource, Pointcut thePointcut) throws IOException {
extractLargeBinaries(theRequestDetails, theResource, thePointcut);
}
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
public void extractLargeBinariesBeforeUpdate(ServletRequestDetails theRequestDetails, IBaseResource thePreviousResource, IBaseResource theResource, Pointcut thePointcut) throws IOException {
blockIllegalExternalBinaryIds(thePreviousResource, theResource);
extractLargeBinaries(theRequestDetails, theResource, thePointcut);
}
/**
* Don't allow clients to submit resources with binary storage attachments declared unless the ID was already in the
* resource. In other words, only HAPI itself may add a binary storage ID extension to a resource unless that
* extension was already present.
*/
private void blockIllegalExternalBinaryIds(IBaseResource thePreviousResource, IBaseResource theResource) {
Set<String> existingBinaryIds = new HashSet<>();
if (thePreviousResource != null) {
List<? extends IPrimitiveType<byte[]>> base64fields = myCtx.newTerser().getAllPopulatedChildElementsOfType(thePreviousResource, myBinaryType);
for (IPrimitiveType<byte[]> nextBase64 : base64fields) {
if (nextBase64 instanceof IBaseHasExtensions) {
((IBaseHasExtensions) nextBase64)
.getExtension()
.stream()
.filter(t -> t.getUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED) == null)
.filter(t -> EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
.map(t -> (IPrimitiveType) t.getValue())
.map(t -> t.getValueAsString())
.filter(t -> isNotBlank(t))
.forEach(t -> existingBinaryIds.add(t));
}
}
}
List<? extends IPrimitiveType<byte[]>> base64fields = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, myBinaryType);
for (IPrimitiveType<byte[]> nextBase64 : base64fields) {
if (nextBase64 instanceof IBaseHasExtensions) {
Optional<String> hasExternalizedBinaryReference = ((IBaseHasExtensions) nextBase64)
.getExtension()
.stream()
.filter(t -> t.getUserData(JpaConstants.EXTENSION_EXT_SYSTEMDEFINED) == null)
.filter(t -> t.getUrl().equals(EXT_EXTERNALIZED_BINARY_ID))
.map(t->(IPrimitiveType) t.getValue())
.map(t->t.getValueAsString())
.filter(t->isNotBlank(t))
.filter(t->{
return !existingBinaryIds.contains(t);
}).findFirst();
if (hasExternalizedBinaryReference.isPresent()) {
String msg = myCtx.getLocalizer().getMessage(BaseHapiFhirDao.class, "externalizedBinaryStorageExtensionFoundInRequestBody", EXT_EXTERNALIZED_BINARY_ID, hasExternalizedBinaryReference.get());
throw new InvalidRequestException(msg);
}
}
}
}
private void extractLargeBinaries(ServletRequestDetails theRequestDetails, IBaseResource theResource, Pointcut thePointcut) throws IOException {
if (theRequestDetails == null) {
// RequestDetails will only be null for internal HAPI events. If externalization is required for them it will need to be done in a different way.
return;
}
IIdType resourceId = theResource.getIdElement();
if (!resourceId.hasResourceType() && resourceId.hasIdPart()) {
String resourceType = myCtx.getResourceDefinition(theResource).getName();
resourceId = new IdType(resourceType + "/" + resourceId.getIdPart());
}
List<IBinaryTarget> attachments = recursivelyScanResourceForBinaryData(theResource);
for (IBinaryTarget nextTarget : attachments) {
byte[] data = nextTarget.getData();
if (data != null && data.length > 0) {
long nextPayloadLength = data.length;
String nextContentType = nextTarget.getContentType();
boolean shouldStoreBlob = myBinaryStorageSvc.shouldStoreBlob(nextPayloadLength, resourceId, nextContentType);
if (shouldStoreBlob) {
String newBlobId;
if (resourceId.hasIdPart()) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(resourceId, null, nextContentType, inputStream);
newBlobId = storedDetails.getBlobId();
} else {
assert thePointcut == Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED : thePointcut.name();
newBlobId = myBinaryStorageSvc.newBlobId();
List<DeferredBinaryTarget> deferredBinaryTargets = getOrCreateDeferredBinaryStorageMap(theRequestDetails);
DeferredBinaryTarget newDeferredBinaryTarget = new DeferredBinaryTarget(newBlobId, nextTarget, data);
deferredBinaryTargets.add(newDeferredBinaryTarget);
}
myBinaryAccessProvider.replaceDataWithExtension(nextTarget, newBlobId);
}
}
}
}
@Nonnull
@SuppressWarnings("unchecked")
private List<DeferredBinaryTarget> getOrCreateDeferredBinaryStorageMap(ServletRequestDetails theRequestDetails) {
List<DeferredBinaryTarget> deferredBinaryTargets = (List<DeferredBinaryTarget>) theRequestDetails.getUserData().get(getDeferredListKey());
if (deferredBinaryTargets == null) {
deferredBinaryTargets = new ArrayList<>();
theRequestDetails.getUserData().put(getDeferredListKey(), deferredBinaryTargets);
}
return deferredBinaryTargets;
}
@SuppressWarnings("unchecked")
@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED)
public void storeLargeBinariesBeforeCreatePersistence(ServletRequestDetails theRequestDetails, IBaseResource theResource, Pointcut thePoincut) throws IOException {
if (theRequestDetails == null) {
return;
}
List<DeferredBinaryTarget> deferredBinaryTargets = (List<DeferredBinaryTarget>) theRequestDetails.getUserData().get(getDeferredListKey());
if (deferredBinaryTargets != null) {
IIdType resourceId = theResource.getIdElement();
for (DeferredBinaryTarget next : deferredBinaryTargets) {
String blobId = next.getBlobId();
IBinaryTarget target = next.getBinaryTarget();
InputStream dataStream = next.getDataStream();
String contentType = target.getContentType();
myBinaryStorageSvc.storeBlob(resourceId, blobId, contentType, dataStream);
}
}
}
private String getDeferredListKey() {
return myDeferredListKey;
}
@Hook(Pointcut.STORAGE_PRESHOW_RESOURCES)
public void preShow(IPreResourceShowDetails theDetails) throws IOException {
long unmarshalledByteCount = 0;
for (IBaseResource nextResource : theDetails) {
IIdType resourceId = nextResource.getIdElement();
List<IBinaryTarget> attachments = recursivelyScanResourceForBinaryData(nextResource);
for (IBinaryTarget nextTarget : attachments) {
Optional<String> attachmentId = nextTarget.getAttachmentId();
if (attachmentId.isPresent()) {
StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(resourceId, attachmentId.get());
if (blobDetails == null) {
String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "unknownBlobId");
throw new InvalidRequestException(msg);
}
if ((unmarshalledByteCount + blobDetails.getBytes()) < myAutoDeExternalizeMaximumBytes) {
byte[] bytes = myBinaryStorageSvc.fetchBlob(resourceId, attachmentId.get());
nextTarget.setData(bytes);
unmarshalledByteCount += blobDetails.getBytes();
}
}
}
}
}
@Nonnull
private List<IBinaryTarget> recursivelyScanResourceForBinaryData(IBaseResource theResource) {
List<IBinaryTarget> binaryTargets = new ArrayList<>();
myCtx.newTerser().visit(theResource, new IModelVisitor2() {
@Override
public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
if (theElement.getClass().equals(myBinaryType)) {
IBase parent = theContainingElementPath.get(theContainingElementPath.size() - 2);
Optional<IBinaryTarget> binaryTarget = myBinaryAccessProvider.toBinaryTarget(parent);
if (binaryTarget.isPresent()) {
binaryTargets.add(binaryTarget.get());
}
}
return true;
}
});
return binaryTargets;
}
private static class DeferredBinaryTarget {
private final String myBlobId;
private final IBinaryTarget myBinaryTarget;
private final InputStream myDataStream;
private DeferredBinaryTarget(String theBlobId, IBinaryTarget theBinaryTarget, byte[] theData) {
myBlobId = theBlobId;
myBinaryTarget = theBinaryTarget;
myDataStream = new ByteArrayInputStream(theData);
}
String getBlobId() {
return myBlobId;
}
IBinaryTarget getBinaryTarget() {
return myBinaryTarget;
}
InputStream getDataStream() {
return myDataStream;
}
}
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.binstore;
import ca.uhn.fhir.jpa.dao.data.IBinaryStorageEntityDao;
import ca.uhn.fhir.jpa.model.entity.BinaryStorageEntity;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import com.google.common.hash.HashingInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CountingInputStream;
@ -57,13 +58,13 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
@Override
@Transactional(Transactional.TxType.SUPPORTS)
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) {
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) {
Date publishedDate = new Date();
HashingInputStream hashingInputStream = createHashingInputStream(theInputStream);
CountingInputStream countingInputStream = createCountingInputStream(hashingInputStream);
String id = newRandomId();
String id = super.provideIdForNewBlob(theBlobIdOrNull);
BinaryStorageEntity entity = new BinaryStorageEntity();
entity.setResourceId(theResourceId.toUnqualifiedVersionless().getValue());
@ -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);
}
}
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.binstore;
*/
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
@ -31,6 +32,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CountingInputStream;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -63,8 +65,8 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
}
@Override
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException {
String id = newRandomId();
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) throws IOException {
String id = super.provideIdForNewBlob(theBlobIdOrNull);
File storagePath = getStoragePath(id, true);
// Write binary file
@ -111,17 +113,31 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
@Override
public boolean writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException {
InputStream inputStream = getInputStream(theResourceId, theBlobId);
if (inputStream != null) {
try {
IOUtils.copy(inputStream, theOutputStream);
theOutputStream.close();
} finally {
inputStream.close();
}
}
return false;
}
@Nullable
private InputStream getInputStream(IIdType theResourceId, String theBlobId) throws FileNotFoundException {
File storagePath = getStoragePath(theBlobId, false);
InputStream inputStream = null;
if (storagePath != null) {
File file = getStorageFilename(storagePath, theResourceId, theBlobId);
if (file.exists()) {
try (InputStream inputStream = new FileInputStream(file)) {
IOUtils.copy(inputStream, theOutputStream);
theOutputStream.close();
}
inputStream = new FileInputStream(file);
}
}
return false;
return inputStream;
}
@Override
@ -139,6 +155,20 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
}
}
@Override
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) throws IOException {
StoredDetails details = fetchBlobDetails(theResourceId, theBlobId);
try (InputStream inputStream = getInputStream(theResourceId, theBlobId)) {
if (inputStream != null) {
return IOUtils.toByteArray(inputStream, details.getBytes());
}
}
throw new ResourceNotFoundException("Unknown blob ID: " + theBlobId + " for resource ID " + theResourceId);
}
private void delete(File theStorageFile, String theBlobId) {
Validate.isTrue(theStorageFile.delete(), "Failed to delete file for blob %s", theBlobId);
}
@ -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);

View File

@ -20,22 +20,12 @@ package ca.uhn.fhir.jpa.binstore;
* #L%
*/
import ca.uhn.fhir.jpa.util.JsonDateDeserializer;
import ca.uhn.fhir.jpa.util.JsonDateSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.hash.HashingInputStream;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
public interface IBinaryStorageSvc {
@ -77,15 +67,22 @@ public interface IBinaryStorageSvc {
*/
boolean shouldStoreBlob(long theSize, IIdType theResourceId, String theContentType);
/**
* Generate a new blob ID that will be passed to {@link #storeBlob(IIdType, String, String, InputStream)} later
*/
String newBlobId();
/**
* Store a new binary blob
*
* @param theResourceId The resource ID that owns this blob. Note that it should not be possible to retrieve a blob without both the resource ID and the blob ID being correct.
* @param 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;
}

View File

@ -0,0 +1,61 @@
package ca.uhn.fhir.jpa.binstore;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Wraps an Attachment datatype or Binary resource, since they both
* hold binary content but don't look entirely similar
*/
interface IBinaryTarget {
void setSize(Integer theSize);
String getContentType();
void setContentType(String theContentType);
byte[] getData();
void setData(byte[] theBytes);
IBaseHasExtensions getTarget();
@SuppressWarnings("unchecked")
default Optional<String> getAttachmentId() {
return getTarget()
.getExtension()
.stream()
.filter(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
.filter(t -> t.getValue() instanceof IPrimitiveType)
.map(t -> (IPrimitiveType<String>) t.getValue())
.map(t -> t.getValue())
.filter(t -> isNotBlank(t))
.findFirst();
}
}

View File

@ -49,8 +49,8 @@ public class MemoryBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl impleme
}
@Override
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException {
String id = newRandomId();
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) throws IOException {
String id = super.provideIdForNewBlob(theBlobIdOrNull);
String key = toKey(theResourceId, id);
HashingInputStream hashingIs = createHashingInputStream(theInputStream);
@ -88,8 +88,18 @@ public class MemoryBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl impleme
myDetailsMap.remove(key);
}
@Override
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) {
String key = toKey(theResourceId, theBlobId);
return myDataMap.get(key);
}
private String toKey(IIdType theResourceId, String theBlobId) {
return theBlobId + '-' + theResourceId.toUnqualifiedVersionless().getValue();
}
public void clear() {
myDetailsMap.clear();
myDataMap.clear();
}
}

View File

@ -53,7 +53,12 @@ public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc {
}
@Override
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) {
public String newBlobId() {
throw new UnsupportedOperationException();
}
@Override
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) {
throw new UnsupportedOperationException();
}
@ -71,4 +76,9 @@ public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc {
public void expungeBlob(IIdType theIdElement, String theBlobId) {
throw new UnsupportedOperationException();
}
@Override
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,130 @@
package ca.uhn.fhir.jpa.binstore;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.util.JsonDateDeserializer;
import ca.uhn.fhir.jpa.util.JsonDateSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.hash.HashingInputStream;
import org.apache.commons.lang3.builder.ToStringBuilder;
import javax.annotation.Nonnull;
import java.util.Date;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class StoredDetails {
@JsonProperty("blobId")
private String myBlobId;
@JsonProperty("bytes")
private long myBytes;
@JsonProperty("contentType")
private String myContentType;
@JsonProperty("hash")
private String myHash;
@JsonProperty("published")
@JsonSerialize(using = JsonDateSerializer.class)
@JsonDeserialize(using = JsonDateDeserializer.class)
private Date myPublished;
/**
* Constructor
*/
@SuppressWarnings("unused")
public StoredDetails() {
super();
}
/**
* Constructor
*/
public StoredDetails(@Nonnull String theBlobId, long theBytes, @Nonnull String theContentType, HashingInputStream theIs, Date thePublished) {
myBlobId = theBlobId;
myBytes = theBytes;
myContentType = theContentType;
myHash = theIs.hash().toString();
myPublished = thePublished;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("blobId", myBlobId)
.append("bytes", myBytes)
.append("contentType", myContentType)
.append("hash", myHash)
.append("published", myPublished)
.toString();
}
public String getHash() {
return myHash;
}
public StoredDetails setHash(String theHash) {
myHash = theHash;
return this;
}
public Date getPublished() {
return myPublished;
}
public StoredDetails setPublished(Date thePublished) {
myPublished = thePublished;
return this;
}
@Nonnull
public String getContentType() {
return myContentType;
}
public StoredDetails setContentType(String theContentType) {
myContentType = theContentType;
return this;
}
@Nonnull
public String getBlobId() {
return myBlobId;
}
public StoredDetails setBlobId(String theBlobId) {
myBlobId = theBlobId;
return this;
}
public long getBytes() {
return myBytes;
}
public StoredDetails setBytes(long theBytes) {
myBytes = theBytes;
return this;
}
}

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