[(master)] Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Simon Marco Janic 2017-11-21 11:15:50 +01:00
commit 46fcceb9c6
67 changed files with 5265 additions and 1207 deletions

2
.gitignore vendored
View File

@ -141,6 +141,8 @@ local.properties
**/.target **/.target
**/.project **/.project
**/.classpath **/.classpath
**/.factorypath
**/.springBeans
# PDT-specific # PDT-specific

7
appveyor.yml Normal file
View File

@ -0,0 +1,7 @@
version: 1.0.{build}
image: Visual Studio 2017
cache:
- C:\maven\
- C:\Users\appveyor\.m2
build_script:
- cmd: mvn -P MINPARALLEL,ALLMODULES install

View File

@ -92,6 +92,12 @@
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId> <artifactId>spring-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
</dependencies> </dependencies>
<reporting> <reporting>

View File

@ -116,10 +116,10 @@
<artifactId>spring-jcl</artifactId> <artifactId>spring-jcl</artifactId>
</dependency> </dependency>
</ignoredDependencies> </ignoredDependencies>
<ignoredResources> <ignoredResourcePatterns>
<ignoredResource>changelog.txt</ignoredResource> <ignoredResourcePattern>changelog.txt</ignoredResourcePattern>
<ignoredResource>javac.bat</ignoredResource> <ignoredResource>javac.bat</ignoredResource>
</ignoredResources> </ignoredResourcePatterns>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@ -43,7 +43,7 @@ public abstract class BaseParser implements IParser {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
private ContainedResources myContainedResources; private ContainedResources myContainedResources;
private boolean myEncodeElementsAppliesToChildResourcesOnly;
private FhirContext myContext; private FhirContext myContext;
private Set<String> myDontEncodeElements; private Set<String> myDontEncodeElements;
private boolean myDontEncodeElementsIncludesStars; private boolean myDontEncodeElementsIncludesStars;
@ -556,6 +556,16 @@ public abstract class BaseParser implements IParser {
&& theIncludedResource == false; && theIncludedResource == false;
} }
@Override
public boolean isEncodeElementsAppliesToChildResourcesOnly() {
return myEncodeElementsAppliesToChildResourcesOnly;
}
@Override
public void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly) {
myEncodeElementsAppliesToChildResourcesOnly = theEncodeElementsAppliesToChildResourcesOnly;
}
@Override @Override
public boolean isOmitResourceId() { public boolean isOmitResourceId() {
return myOmitResourceId; return myOmitResourceId;
@ -1051,7 +1061,13 @@ public abstract class BaseParser implements IParser {
} }
private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) { private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, myEncodeElements, true); Set<String> encodeElements = myEncodeElements;
if (encodeElements != null && encodeElements.isEmpty() == false) {
if (isEncodeElementsAppliesToChildResourcesOnly() && !mySubResource) {
encodeElements = null;
}
}
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, encodeElements, true);
} }
private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) { private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
@ -1070,6 +1086,9 @@ public abstract class BaseParser implements IParser {
} else { } else {
thePathBuilder.append(myResDef.getName()); thePathBuilder.append(myResDef.getName());
} }
if (theElements == null) {
return true;
}
if (theElements.contains(thePathBuilder.toString())) { if (theElements.contains(thePathBuilder.toString())) {
return true; return true;
} }

View File

@ -206,6 +206,22 @@ public interface IParser {
*/ */
void setEncodeElements(Set<String> theEncodeElements); void setEncodeElements(Set<String> theEncodeElements);
/**
* If set to <code>true</code> (default is false), the values supplied
* to {@link #setEncodeElements(Set)} will not be applied to the root
* resource (typically a Bundle), but will be applied to any sub-resources
* contained within it (i.e. search result resources in that bundle)
*/
void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly);
/**
* If set to <code>true</code> (default is false), the values supplied
* to {@link #setEncodeElements(Set)} will not be applied to the root
* resource (typically a Bundle), but will be applied to any sub-resources
* contained within it (i.e. search result resources in that bundle)
*/
boolean isEncodeElementsAppliesToChildResourcesOnly();
/** /**
* If provided, tells the parse which resource types to apply {@link #setEncodeElements(Set) encode elements} to. Any * If provided, tells the parse which resource types to apply {@link #setEncodeElements(Set) encode elements} to. Any
* resource types not specified here will be encoded completely, with no elements excluded. * resource types not specified here will be encoded completely, with no elements excluded.

View File

@ -19,11 +19,12 @@ public class BinaryUtil {
public static IBaseReference getSecurityContext(FhirContext theCtx, IBaseBinary theBinary) { public static IBaseReference getSecurityContext(FhirContext theCtx, IBaseBinary theBinary) {
RuntimeResourceDefinition def = theCtx.getResourceDefinition("Binary"); RuntimeResourceDefinition def = theCtx.getResourceDefinition("Binary");
BaseRuntimeChildDefinition child = def.getChildByName("securityContext"); BaseRuntimeChildDefinition child = def.getChildByName("securityContext");
List<IBase> values = child.getAccessor().getValues(theBinary);
IBaseReference retVal = null; IBaseReference retVal = null;
if (values.size() > 0) { if (child != null) {
retVal = (IBaseReference) values.get(0); List<IBase> values = child.getAccessor().getValues(theBinary);
if (values.size() > 0) {
retVal = (IBaseReference) values.get(0);
}
} }
return retVal; return retVal;
} }

View File

@ -21,12 +21,14 @@ package ca.uhn.fhir.util;
*/ */
import java.net.ServerSocket; import java.net.ServerSocket;
import java.util.LinkedHashSet;
/** /**
* Provides server ports * Provides server ports
*/ */
@CoverageIgnore @CoverageIgnore
public class PortUtil { public class PortUtil {
private static LinkedHashSet<Integer> ourPorts = new LinkedHashSet<>();
/* /*
* Non instantiable * Non instantiable
@ -41,9 +43,13 @@ public class PortUtil {
public static int findFreePort() { public static int findFreePort() {
ServerSocket server; ServerSocket server;
try { try {
server = new ServerSocket(0); int port;
int port = server.getLocalPort(); do {
server.close(); server = new ServerSocket(0);
port = server.getLocalPort();
server.close();
} while (!ourPorts.add(port));
Thread.sleep(500); Thread.sleep(500);
return port; return port;
} catch (Exception e) { } catch (Exception e) {

View File

@ -30,8 +30,10 @@ import org.slf4j.LoggerFactory;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.defaultString;
@ -123,6 +125,27 @@ public class TestUtil {
ourLog.info("Tests are using time zone: {}", TimeZone.getDefault().getID()); ourLog.info("Tests are using time zone: {}", TimeZone.getDefault().getID());
} }
/**
* <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
* <p>
* Wait for an atomicinteger to hit a given site and fail if it never does
*/
public static void waitForSize(int theTarget, AtomicInteger theInteger) {
long start = System.currentTimeMillis();
while (theInteger.get() != theTarget && (System.currentTimeMillis() - start) <= 15000) {
try {
Thread.sleep(50);
} catch (InterruptedException theE) {
throw new Error(theE);
}
}
if ((System.currentTimeMillis() - start) >= 15000) {
throw new IllegalStateException("Size " + theInteger.get() + " is != target " + theTarget);
}
}
/** /**
* <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b> * <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
* <p> * <p>

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.demo;
import java.util.Properties; import java.util.Properties;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -23,6 +24,7 @@ public class FhirDbConfig {
extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -0,0 +1,96 @@
package ca.uhn.fhir.rest.client.interceptor;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* This interceptor adds arbitrary header values to requests made by the client.
*/
public class AdditionalRequestHeadersInterceptor implements IClientInterceptor {
private final Map<String, List<String>> additionalHttpHeaders = new HashMap<>();
public AdditionalRequestHeadersInterceptor() {
this(new HashMap<String, List<String>>());
}
public AdditionalRequestHeadersInterceptor(Map<String, List<String>> additionalHttpHeaders) {
super();
if (additionalHttpHeaders != null) {
this.additionalHttpHeaders.putAll(additionalHttpHeaders);
}
}
/**
* Adds the given header value.
* Note that {@code headerName} and {@code headerValue} cannot be null.
* @param headerName the name of the header
* @param headerValue the value to add for the header
* @throws NullPointerException if either parameter is {@code null}
*/
public void addHeaderValue(String headerName, String headerValue) {
Objects.requireNonNull(headerName, "headerName cannot be null");
Objects.requireNonNull(headerValue, "headerValue cannot be null");
getHeaderValues(headerName).add(headerValue);
}
/**
* Adds the list of header values for the given header.
* Note that {@code headerName} and {@code headerValues} cannot be null.
* @param headerName the name of the header
* @param headerValues the list of values to add for the header
* @throws NullPointerException if either parameter is {@code null}
*/
public void addAllHeaderValues(String headerName, List<String> headerValues) {
Objects.requireNonNull(headerName, "headerName cannot be null");
Objects.requireNonNull(headerValues, "headerValues cannot be null");
getHeaderValues(headerName).addAll(headerValues);
}
/**
* Gets the header values list for a given header.
* If the header doesn't have any values, an empty list will be returned.
* @param headerName the name of the header
* @return the list of values for the header
*/
private List<String> getHeaderValues(String headerName) {
if (additionalHttpHeaders.get(headerName) == null) {
additionalHttpHeaders.put(headerName, new ArrayList<String>());
}
return additionalHttpHeaders.get(headerName);
}
/**
* Adds the additional header values to the HTTP request.
* @param theRequest the HTTP request
*/
@Override
public void interceptRequest(IHttpRequest theRequest) {
for (Map.Entry<String, List<String>> header : additionalHttpHeaders.entrySet()) {
for (String headerValue : header.getValue()) {
if (headerValue != null) {
theRequest.addHeader(header.getKey(), headerValue);
}
}
}
}
/**
* Does nothing since this interceptor is not concerned with the response.
* @param theResponse the HTTP response
* @throws IOException
*/
@Override
public void interceptResponse(IHttpResponse theResponse) throws IOException {
// Do nothing. This interceptor is not concerned with the response.
}
}

View File

@ -32,6 +32,8 @@ import org.apache.commons.lang3.Validate;
/** /**
* This interceptor adds an arbitrary header to requests made by this client. Both the * This interceptor adds an arbitrary header to requests made by this client. Both the
* header name and the header value are specified by the calling code. * header name and the header value are specified by the calling code.
*
* @see AdditionalRequestHeadersInterceptor for a more advanced version of this interceptor which can add multiple headers
*/ */
public class SimpleRequestHeaderInterceptor implements IClientInterceptor { public class SimpleRequestHeaderInterceptor implements IClientInterceptor {

View File

@ -26,20 +26,8 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.lucene.analysis.core.LowerCaseFilterFactory;
import org.apache.lucene.analysis.core.StopFilterFactory;
import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilterFactory;
import org.apache.lucene.analysis.ngram.EdgeNGramFilterFactory;
import org.apache.lucene.analysis.ngram.NGramFilterFactory;
import org.apache.lucene.analysis.pattern.PatternTokenizerFactory;
import org.apache.lucene.analysis.phonetic.PhoneticFilterFactory;
import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory;
import org.apache.lucene.analysis.standard.StandardFilterFactory;
import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OptimisticLock; import org.hibernate.annotations.OptimisticLock;
import org.hibernate.search.annotations.*; import org.hibernate.search.annotations.*;
import org.hibernate.search.annotations.Parameter;
import javax.persistence.*; import javax.persistence.*;
import javax.persistence.Index; import javax.persistence.Index;
@ -61,53 +49,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
@Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"), @Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"),
@Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS") @Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS")
}) })
@AnalyzerDefs({
@AnalyzerDef(name = "autocompleteEdgeAnalyzer",
tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params = {
@Parameter(name = "pattern", value = "(.*)"),
@Parameter(name = "group", value = "1")
}),
filters = {
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = StopFilterFactory.class),
@TokenFilterDef(factory = EdgeNGramFilterFactory.class, params = {
@Parameter(name = "minGramSize", value = "3"),
@Parameter(name = "maxGramSize", value = "50")
}),
}),
@AnalyzerDef(name = "autocompletePhoneticAnalyzer",
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = StandardFilterFactory.class),
@TokenFilterDef(factory = StopFilterFactory.class),
@TokenFilterDef(factory = PhoneticFilterFactory.class, params = {
@Parameter(name = "encoder", value = "DoubleMetaphone")
}),
@TokenFilterDef(factory = SnowballPorterFilterFactory.class, params = {
@Parameter(name = "language", value = "English")
})
}),
@AnalyzerDef(name = "autocompleteNGramAnalyzer",
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = WordDelimiterFilterFactory.class),
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = NGramFilterFactory.class, params = {
@Parameter(name = "minGramSize", value = "3"),
@Parameter(name = "maxGramSize", value = "20")
}),
}),
@AnalyzerDef(name = "standardAnalyzer",
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
}),
@AnalyzerDef(name = "exactAnalyzer",
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
})
}
)
//@formatter:on //@formatter:on
public class ResourceTable extends BaseHasResource implements Serializable { public class ResourceTable extends BaseHasResource implements Serializable {
static final int RESTYPE_LEN = 30; static final int RESTYPE_LEN = 30;

View File

@ -52,16 +52,12 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory;
import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Analyzer; import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.AnalyzerDef;
import org.hibernate.search.annotations.AnalyzerDefs;
import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Fields; import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store; import org.hibernate.search.annotations.Store;
import org.hibernate.search.annotations.TokenizerDef;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor;
@ -73,12 +69,6 @@ import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor;
}, indexes= { }, indexes= {
@Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList="INDEX_STATUS") @Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList="INDEX_STATUS")
}) })
@AnalyzerDefs({
@AnalyzerDef(name = "conceptParentPidsAnalyzer",
tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
filters = {
})
})
public class TermConcept implements Serializable { public class TermConcept implements Serializable {
private static final int MAX_DESC_LENGTH = 400; private static final int MAX_DESC_LENGTH = 400;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermConcept.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermConcept.class);

View File

@ -0,0 +1,53 @@
package ca.uhn.fhir.jpa.search;
import org.apache.lucene.analysis.core.LowerCaseFilterFactory;
import org.apache.lucene.analysis.core.StopFilterFactory;
import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory;
import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilterFactory;
import org.apache.lucene.analysis.ngram.EdgeNGramFilterFactory;
import org.apache.lucene.analysis.ngram.NGramFilterFactory;
import org.apache.lucene.analysis.pattern.PatternTokenizerFactory;
import org.apache.lucene.analysis.phonetic.PhoneticFilterFactory;
import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory;
import org.apache.lucene.analysis.standard.StandardFilterFactory;
import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
import org.hibernate.search.annotations.Factory;
import org.hibernate.search.cfg.SearchMapping;
/**
* Factory for defining the analysers.
*/
public class LuceneSearchMappingFactory {
@Factory
public SearchMapping getSearchMapping() {
SearchMapping mapping = new SearchMapping();
mapping.analyzerDef("autocompleteEdgeAnalyzer", PatternTokenizerFactory.class)
.tokenizerParam("pattern", "(.*)")
.tokenizerParam("group", "1")
.filter(LowerCaseFilterFactory.class)
.filter(StopFilterFactory.class)
.filter(EdgeNGramFilterFactory.class)
.param("minGramSize", "3")
.param("maxGramSize", "50")
.analyzerDef("autocompletePhoneticAnalyzer", StandardTokenizerFactory.class)
.filter(StandardFilterFactory.class)
.filter(StopFilterFactory.class)
.filter(PhoneticFilterFactory.class)
.param("encoder", "DoubleMetaphone")
.filter(SnowballPorterFilterFactory.class)
.param("language", "English")
.analyzerDef("autocompleteNGramAnalyzer", StandardTokenizerFactory.class)
.filter(WordDelimiterFilterFactory.class)
.filter(LowerCaseFilterFactory.class)
.filter(NGramFilterFactory.class)
.param("minGramSize", "3")
.param("maxGramSize", "20")
.analyzerDef("standardAnalyzer", StandardTokenizerFactory.class)
.filter(LowerCaseFilterFactory.class)
.analyzerDef("exactAnalyzer", StandardTokenizerFactory.class)
.analyzerDef("conceptParentPidsAnalyzer", WhitespaceTokenizerFactory.class);
return mapping;
}
}

View File

@ -19,39 +19,51 @@ package ca.uhn.fhir.jpa.search;
* limitations under the License. * limitations under the License.
* #L%family * #L%family
*/ */
import java.util.*;
import java.util.concurrent.*;
import javax.persistence.EntityManager;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.method.PageMethodBinding;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.transaction.*; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.*; import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.google.common.annotations.VisibleForTesting; import javax.persistence.EntityManager;
import com.google.common.collect.Lists; import java.util.*;
import java.util.concurrent.*;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.data.*;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.method.PageMethodBinding;
public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
public static final int DEFAULT_SYNC_SIZE = 250; public static final int DEFAULT_SYNC_SIZE = 250;
@ -420,6 +432,19 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
} }
} }
/**
* A search task is a Callable task that runs in
* a thread pool to handle an individual search. One instance
* is created for any requested search and runs from the
* beginning to the end of the search.
*
* Understand:
* This class executes in its own thread separate from the
* web server client thread that made the request. We do that
* so that we can return to the client as soon as possible,
* but keep the search going in the background (and have
* the next page of results ready to go when the client asks).
*/
public class SearchTask implements Callable<Void> { public class SearchTask implements Callable<Void> {
private final IDao myCallingDao; private final IDao myCallingDao;
@ -434,6 +459,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
private int myCountSaved = 0; private int myCountSaved = 0;
private String mySearchUuid; private String mySearchUuid;
/**
* Constructor
*/
public SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, String theSearchUuid) { public SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, String theSearchUuid) {
mySearch = theSearch; mySearch = theSearch;
myCallingDao = theCallingDao; myCallingDao = theCallingDao;
@ -443,6 +471,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
mySearchUuid = theSearchUuid; mySearchUuid = theSearchUuid;
} }
/**
* This method is called by the server HTTP thread, and
* will block until at least one page of results have been
* fetched from the DB, and will never block after that.
*/
public Integer awaitInitialSync() { public Integer awaitInitialSync() {
ourLog.trace("Awaiting initial sync"); ourLog.trace("Awaiting initial sync");
do { do {
@ -451,6 +484,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
break; break;
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
// Shouldn't happen
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
} while (mySearch.getStatus() == SearchStatusEnum.LOADING); } while (mySearch.getStatus() == SearchStatusEnum.LOADING);
@ -459,11 +493,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
return mySearch.getTotalCount(); return mySearch.getTotalCount();
} }
/**
* This is the method which actually performs the search.
* It is called automatically by the thread pool.
*/
@Override @Override
public Void call() throws Exception { public Void call() throws Exception {
StopWatch sw = new StopWatch(); StopWatch sw = new StopWatch();
try { try {
// Create an initial search in the DB and give it an ID
saveSearch(); saveSearch();
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
@ -480,7 +519,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
} catch (Throwable t) { } catch (Throwable t) {
/* /*
* Don't print a stack trace for client errors.. that's just noisy * Don't print a stack trace for client errors (i.e. requests that
* aren't valid because the client screwed up).. that's just noise
* in the logs and who needs that.
*/ */
boolean logged = false; boolean logged = false;
if (t instanceof BaseServerResponseException) { if (t instanceof BaseServerResponseException) {
@ -535,13 +576,27 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
Class<? extends IBaseResource> resourceTypeClass = myContext.getResourceDefinition(myResourceType).getImplementingClass(); Class<? extends IBaseResource> resourceTypeClass = myContext.getResourceDefinition(myResourceType).getImplementingClass();
ISearchBuilder sb = myCallingDao.newSearchBuilder(); ISearchBuilder sb = myCallingDao.newSearchBuilder();
sb.setType(resourceTypeClass, myResourceType); sb.setType(resourceTypeClass, myResourceType);
Iterator<Long> theResultIter = sb.createQuery(myParams, mySearchUuid); Iterator<Long> theResultIterator = sb.createQuery(myParams, mySearchUuid);
while (theResultIter.hasNext()) { while (theResultIterator.hasNext()) {
myUnsyncedPids.add(theResultIter.next()); myUnsyncedPids.add(theResultIterator.next());
if (myUnsyncedPids.size() >= mySyncSize) {
saveUnsynced(theResultIter); boolean shouldSync = myUnsyncedPids.size() >= mySyncSize;
if (myDaoConfig.getCountSearchResultsUpTo() != null &&
myDaoConfig.getCountSearchResultsUpTo() > 0 &&
myDaoConfig.getCountSearchResultsUpTo() < myUnsyncedPids.size()) {
shouldSync = false;
} }
if (myUnsyncedPids.size() > 50000) {
shouldSync = true;
}
if (shouldSync) {
saveUnsynced(theResultIterator);
}
if (myLoadingThrottleForUnitTests != null) { if (myLoadingThrottleForUnitTests != null) {
try { try {
Thread.sleep(myLoadingThrottleForUnitTests); Thread.sleep(myLoadingThrottleForUnitTests);
@ -549,9 +604,12 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
// ignore // ignore
} }
} }
// Check if an abort got requested
Validate.isTrue(myAbortRequested == false, "Abort has been requested"); Validate.isTrue(myAbortRequested == false, "Abort has been requested");
} }
saveUnsynced(theResultIter); saveUnsynced(theResultIterator);
} }
public CountDownLatch getCompletionLatch() { public CountDownLatch getCompletionLatch() {
@ -648,22 +706,25 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
mySearch.setStatus(SearchStatusEnum.FINISHED); mySearch.setStatus(SearchStatusEnum.FINISHED);
} }
} }
mySearch.setNumFound(myCountSaved); mySearch.setNumFound(myCountSaved);
int numSynced;
synchronized (mySyncedPids) {
numSynced = mySyncedPids.size();
}
if (myDaoConfig.getCountSearchResultsUpTo() == null ||
myDaoConfig.getCountSearchResultsUpTo() <= 0 ||
myDaoConfig.getCountSearchResultsUpTo() <= numSynced) {
myInitialCollectionLatch.countDown();
}
doSaveSearch(); doSaveSearch();
} }
}); });
int numSynced;
synchronized (mySyncedPids) {
numSynced = mySyncedPids.size();
}
if (myDaoConfig.getCountSearchResultsUpTo() == null ||
myDaoConfig.getCountSearchResultsUpTo() <= 0 ||
myDaoConfig.getCountSearchResultsUpTo() <= numSynced) {
myInitialCollectionLatch.countDown();
}
} }
} }

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
@ -60,6 +60,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.default.directory_provider", "ram");
extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT");
return extraProperties; return extraProperties;

View File

@ -1,5 +1,15 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.subscription.email.IEmailSender; import ca.uhn.fhir.jpa.subscription.email.IEmailSender;
import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender; import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender;
@ -141,6 +151,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.default.directory_provider", "ram");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.autoregister_listeners", "true"); extraProperties.put("hibernate.search.autoregister_listeners", "true");

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder; import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
@ -125,6 +126,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect");
extraProperties.put("hibernate.search.model_mapping", ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.default.directory_provider", "ram");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.autoregister_listeners", "true"); extraProperties.put("hibernate.search.autoregister_listeners", "true");

View File

@ -56,6 +56,30 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical()); myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical());
} }
/**
* See #773
*/
@Test
public void testDeleteResourceWithOutboundDeletedResources() {
myDaoConfig.setEnforceReferentialIntegrityOnDelete(false);
Organization org = new Organization();
org.setId("ORG");
org.setName("ORG");
myOrganizationDao.update(org);
Patient pat = new Patient();
pat.setId("PAT");
pat.setActive(true);
pat.setManagingOrganization(new ResourceReferenceDt("Organization/ORG"));
myPatientDao.update(pat);
myOrganizationDao.delete(new IdDt("Organization/ORG"));
myPatientDao.delete(new IdDt("Patient/PAT"));
}
private void assertGone(IIdType theId) { private void assertGone(IIdType theId) {
try { try {
assertNotGone(theId); assertNotGone(theId);

View File

@ -55,6 +55,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
public final void after() { public final void after() {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical()); myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical());
myDaoConfig.setEnforceReferentialIntegrityOnDelete(new DaoConfig().isEnforceReferentialIntegrityOnDelete());
} }
private void assertGone(IIdType theId) { private void assertGone(IIdType theId) {
@ -98,6 +99,29 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
} }
/**
* See #773
*/
@Test
public void testDeleteResourceWithOutboundDeletedResources() {
myDaoConfig.setEnforceReferentialIntegrityOnDelete(false);
Organization org = new Organization();
org.setId("ORG");
org.setName("ORG");
myOrganizationDao.update(org);
Patient pat = new Patient();
pat.setId("PAT");
pat.setActive(true);
pat.setManagingOrganization(new Reference("Organization/ORG"));
myPatientDao.update(pat);
myOrganizationDao.delete(new IdType("Organization/ORG"));
myPatientDao.delete(new IdType("Patient/PAT"));
}
@Before @Before
public void beforeDisableResultReuse() { public void beforeDisableResultReuse() {

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.provider.dstu3; package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.provider.r4.ResourceProviderInterceptorR4Test; import ca.uhn.fhir.jpa.provider.r4.ResourceProviderInterceptorR4Test;
import ca.uhn.fhir.model.dstu2.resource.Conformance;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
@ -61,8 +60,8 @@ public class ResourceProviderInterceptorDstu3Test extends BaseResourceProviderDs
public void before() throws Exception { public void before() throws Exception {
super.before(); super.before();
myServerInterceptor = mock(IServerInterceptor.class); myServerInterceptor = mock(IServerInterceptor.class, withSettings().verboseLogging());
myDaoInterceptor = mock(IServerInterceptor.class); myDaoInterceptor = mock(IServerInterceptor.class, withSettings().verboseLogging());
resetServerInterceptor(); resetServerInterceptor();
@ -125,6 +124,8 @@ public class ResourceProviderInterceptorDstu3Test extends BaseResourceProviderDs
public void testCreateResourceInTransaction() throws IOException, ServletException { public void testCreateResourceInTransaction() throws IOException, ServletException {
String methodName = "testCreateResourceInTransaction"; String methodName = "testCreateResourceInTransaction";
ourLog.info("** Starting {}", methodName);
Patient pt = new Patient(); Patient pt = new Patient();
pt.addName().setFamily(methodName); pt.addName().setFamily(methodName);
@ -140,6 +141,11 @@ public class ResourceProviderInterceptorDstu3Test extends BaseResourceProviderDs
resetServerInterceptor(); resetServerInterceptor();
ArgumentCaptor<ActionRequestDetails> ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class);
ArgumentCaptor<RestOperationTypeEnum> opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class);
verify(myDaoInterceptor, times(0)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture());
verify(myServerInterceptor, times(0)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture());
HttpPost post = new HttpPost(ourServerBase + "/"); HttpPost post = new HttpPost(ourServerBase + "/");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post); CloseableHttpResponse response = ourHttpClient.execute(post);
@ -153,8 +159,8 @@ public class ResourceProviderInterceptorDstu3Test extends BaseResourceProviderDs
* Server Interceptor * Server Interceptor
*/ */
ArgumentCaptor<ActionRequestDetails> ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class);
ArgumentCaptor<RestOperationTypeEnum> opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class);
verify(myServerInterceptor, times(2)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); verify(myServerInterceptor, times(2)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture());
assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0)); assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0));
assertEquals(null, ardCaptor.getAllValues().get(0).getResourceType()); assertEquals(null, ardCaptor.getAllValues().get(0).getResourceType());

View File

@ -3149,17 +3149,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
.count(1) .count(1)
.execute(); .execute();
ourLog.info("** Done searching with count of 1"); ourLog.info("** Done searching in {}ms with count of 1", sw.getMillis());
ourLog.info(myCapturingInterceptor.getLastResponse().getAllHeaders().toString()); ourLog.info(myCapturingInterceptor.getLastResponse().getAllHeaders().toString());
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.<String>empty()); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.<String>empty());
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.<String>empty()); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.<String>empty());
assertThat(sw.getMillis(), lessThan(1000L)); String msg = "Total is " + found.getTotalElement().getValue() + " and took " + sw.getMillis() + " millis";
// If this fails under load, try increasing the throttle above // If this fails under load, try increasing the throttle above
assertEquals(null, found.getTotalElement().getValue()); assertEquals(msg,null, found.getTotalElement().getValue());
assertEquals(1, found.getEntry().size()); assertEquals(msg, 1, found.getEntry().size());
assertThat(msg, sw.getMillis(), lessThan(1000L));
} }
@Test @Test
@ -3214,11 +3216,10 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.<String>empty()); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.<String>empty());
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.<String>empty()); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.<String>empty());
assertThat(sw.getMillis(), lessThan(1000L));
// If this fails under load, try increasing the throttle above // If this fails under load, try increasing the throttle above
assertEquals(null, found.getTotalElement().getValue()); assertEquals(null, found.getTotalElement().getValue());
assertEquals(1, found.getEntry().size()); assertEquals(1, found.getEntry().size());
assertThat(sw.getMillis(), lessThan(1000L));
} }
@Test @Test

View File

@ -5,6 +5,7 @@ import java.util.Properties;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.HibernatePersistenceProvider;
@ -80,6 +81,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -1,10 +1,9 @@
package ca.uhn.fhir.jpa.demo; package ca.uhn.fhir.jpa.demo;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
@ -80,6 +79,7 @@ public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 {
extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -84,6 +84,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@ -111,6 +112,7 @@ public class TdlDstu2Config extends BaseJavaConfigDstu2 {
extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider" ,"filesystem"); extraProperties.put("hibernate.search.default.directory_provider" ,"filesystem");
extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation);
extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT");

View File

@ -5,6 +5,7 @@ import java.util.Properties;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.DerbyTenSevenDialect; import org.hibernate.dialect.DerbyTenSevenDialect;
@ -13,7 +14,6 @@ import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
@ -94,6 +94,7 @@ public class TdlDstu3Config extends BaseJavaConfigDstu3 {
extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation);
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
@ -65,6 +66,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
retVal.setAllowExternalReferences(true); retVal.setAllowExternalReferences(true);
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu2"); retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu2");
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu2"); retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu2");
retVal.setCountSearchResultsUpTo(TestR4Config.COUNT_SEARCH_RESULTS_UP_TO);
return retVal; return retVal;
} }
@ -115,6 +117,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation);
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -5,6 +5,7 @@ import java.util.Properties;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.DerbyTenSevenDialect; import org.hibernate.dialect.DerbyTenSevenDialect;
@ -54,6 +55,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
retVal.setAllowExternalReferences(true); retVal.setAllowExternalReferences(true);
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu3"); retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu3");
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu3"); retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu3");
retVal.setCountSearchResultsUpTo(TestR4Config.COUNT_SEARCH_RESULTS_UP_TO);
return retVal; return retVal;
} }
@ -112,6 +114,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation);
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -5,11 +5,11 @@ import java.util.Properties;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.DerbyTenSevenDialect; import org.hibernate.dialect.DerbyTenSevenDialect;
import org.hibernate.dialect.PostgreSQL94Dialect; import org.hibernate.dialect.PostgreSQL94Dialect;
import org.hibernate.dialect.PostgreSQL95Dialect;
import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -110,6 +110,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName());
extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation);
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -86,10 +86,41 @@ public class RestfulServerUtils {
} }
} }
if (elements != null && elements.size() > 0) { if (elements != null && elements.size() > 0) {
Set<String> newElements = new HashSet<String>(); Set<String> newElements = new HashSet<>();
for (String next : elements) { for (String next : elements) {
newElements.add("*." + next); newElements.add("*." + next);
} }
/*
* We try to be smart about what the user is asking for
* when they include an _elements parameter. If we're responding
* to something that returns a Bundle (e.g. a search) we assume
* the elements don't apply to the Bundle itself, unless
* the client has explicitly scoped the Bundle
* (i.e. with Bundle.total or something like that)
*/
switch (theRequestDetails.getRestOperationType()) {
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case HISTORY_INSTANCE:
case GET_PAGE:
boolean haveExplicitBundleElement = false;
for (String next : newElements) {
if (next.startsWith("Bundle.")) {
haveExplicitBundleElement = true;
break;
}
}
if (!haveExplicitBundleElement) {
parser.setEncodeElementsAppliesToChildResourcesOnly(true);
}
break;
default:
break;
}
parser.setEncodeElements(newElements); parser.setEncodeElements(newElements);
parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo); parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo);
} }
@ -147,6 +178,19 @@ public class RestfulServerUtils {
b.append(theBundleType.getCode()); b.append(theBundleType.getCode());
} }
String paramName = Constants.PARAM_ELEMENTS;
String[] params = theRequestParameters.get(paramName);
if (params != null) {
for (String nextValue : params) {
if (isNotBlank(nextValue)) {
b.append('&');
b.append(paramName);
b.append('=');
b.append(UrlUtil.escape(nextValue));
}
}
}
return b.toString(); return b.toString();
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported", e);// should not happen throw new Error("UTF-8 not supported", e);// should not happen
@ -587,9 +631,11 @@ public class RestfulServerUtils {
response.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;"); response.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
IBaseReference securityContext = BinaryUtil.getSecurityContext(theServer.getFhirContext(), bin); IBaseReference securityContext = BinaryUtil.getSecurityContext(theServer.getFhirContext(), bin);
String securityContextRef = securityContext.getReferenceElement().getValue(); if (securityContext != null) {
if (isNotBlank(securityContextRef)) { String securityContextRef = securityContext.getReferenceElement().getValue();
response.addHeader(Constants.HEADER_X_SECURITY_CONTEXT, securityContextRef); if (isNotBlank(securityContextRef)) {
response.addHeader(Constants.HEADER_X_SECURITY_CONTEXT, securityContextRef);
}
} }
return response.sendAttachmentResponse(bin, theStausCode, contentType); return response.sendAttachmentResponse(bin, theStausCode, contentType);

View File

@ -332,7 +332,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
} }
bundleFactory.addRootPropertiesToBundle(theResult.getUuid(), serverBase, theLinkSelf, linkPrev, linkNext, theResult.size(), theBundleType, theResult.getPublished()); bundleFactory.addRootPropertiesToBundle(theResult.getUuid(), serverBase, theLinkSelf, linkPrev, linkNext, theResult.size(), theBundleType, theResult.getPublished());
bundleFactory.addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes); bundleFactory.addResourcesToBundle(new ArrayList<>(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes);
if (theServer.getPagingProvider() != null) { if (theServer.getPagingProvider() != null) {
int limit; int limit;

View File

@ -0,0 +1,118 @@
<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>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-autoconfigure</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Compile dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- Optional dependencies -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-server</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-base</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jaxrsserver-base</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client-okhttp</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<optional>true</optional>
</dependency>
<!-- @ConfigurationProperties annotation processing (metadata for IDEs) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu2.1</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu3</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,287 @@
package ca.uhn.fhir.spring.boot.autoconfigure;
import java.util.List;
import javax.servlet.ServletException;
import javax.sql.DataSource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.BaseJpaProvider;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider;
import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory;
import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
import okhttp3.OkHttpClient;
import org.apache.http.client.HttpClient;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ResourceCondition;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.util.CollectionUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for HAPI FHIR.
*
* @author Mathieu Ouellet
*/
@Configuration
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
@EnableConfigurationProperties(FhirProperties.class)
public class FhirAutoConfiguration {
private final FhirProperties properties;
public FhirAutoConfiguration(FhirProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public FhirContext fhirContext() {
FhirContext fhirContext = new FhirContext(properties.getVersion());
return fhirContext;
}
@Configuration
@ConditionalOnClass(AbstractJaxRsProvider.class)
@EnableConfigurationProperties(FhirProperties.class)
@ConfigurationProperties("hapi.fhir.rest")
@SuppressWarnings("serial")
static class FhirRestfulServerConfiguration extends RestfulServer {
private final FhirProperties properties;
private final FhirContext fhirContext;
private final List<IResourceProvider> resourceProviders;
private final IPagingProvider pagingProvider;
private final List<IServerInterceptor> interceptors;
private final List<FhirRestfulServerCustomizer> customizers;
public FhirRestfulServerConfiguration(
FhirProperties properties,
FhirContext fhirContext,
ObjectProvider<List<IResourceProvider>> resourceProviders,
ObjectProvider<IPagingProvider> pagingProvider,
ObjectProvider<List<IServerInterceptor>> interceptors,
ObjectProvider<List<FhirRestfulServerCustomizer>> customizers) {
this.properties = properties;
this.fhirContext = fhirContext;
this.resourceProviders = resourceProviders.getIfAvailable();
this.pagingProvider = pagingProvider.getIfAvailable();
this.interceptors = interceptors.getIfAvailable();
this.customizers = customizers.getIfAvailable();
}
@Bean
public ServletRegistrationBean fhirServerRegistrationBean() {
ServletRegistrationBean registration = new ServletRegistrationBean(this, this.properties.getServer().getPath());
registration.setLoadOnStartup(1);
return registration;
}
@Override
protected void initialize() throws ServletException {
super.initialize();
setFhirContext(this.fhirContext);
setResourceProviders(this.resourceProviders);
setPagingProvider(this.pagingProvider);
setInterceptors(this.interceptors);
setServerAddressStrategy(new HardcodedServerAddressStrategy(this.properties.getServer().getPath()));
customize();
}
private void customize() {
if (this.customizers != null) {
AnnotationAwareOrderComparator.sort(this.customizers);
for (FhirRestfulServerCustomizer customizer : this.customizers) {
customizer.customize(this);
}
}
}
}
@Configuration
@ConditionalOnClass(BaseJpaProvider.class)
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(FhirProperties.class)
static class FhirJpaServerConfiguration {
@Configuration
@EntityScan("ca.uhn.fhir.jpa.entity")
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
static class FhirJpaDaoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConfigurationProperties("hapi.fhir.jpa")
public DaoConfig fhirDaoConfig() {
DaoConfig fhirDaoConfig = new DaoConfig();
return fhirDaoConfig;
}
}
@Configuration
@ConditionalOnBean({ DaoConfig.class, RestfulServer.class })
@SuppressWarnings("rawtypes")
static class RestfulServerCustomizer implements FhirRestfulServerCustomizer {
private final BaseJpaSystemProvider systemProviders;
public RestfulServerCustomizer(ObjectProvider<BaseJpaSystemProvider> systemProviders) {
this.systemProviders = systemProviders.getIfAvailable();
}
@Override
public void customize(RestfulServer server) {
server.setPlainProviders(systemProviders);
}
}
@Configuration
@ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig")
@ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU3")
static class Dstu3 extends BaseJavaConfigDstu3 {
}
@Configuration
@ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig")
@ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU2")
static class Dstu2 extends BaseJavaConfigDstu2 {
}
}
@Configuration
@Conditional(FhirValidationConfiguration.SchemaAvailableCondition.class)
@ConditionalOnProperty(name = "hapi.fhir.validation.enabled", matchIfMissing = true)
static class FhirValidationConfiguration {
@Bean
@ConditionalOnMissingBean
public RequestValidatingInterceptor requestValidatingInterceptor() {
return new RequestValidatingInterceptor();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "hapi.fhir.validation.request-only", havingValue = "false")
public ResponseValidatingInterceptor responseValidatingInterceptor() {
return new ResponseValidatingInterceptor();
}
static class SchemaAvailableCondition extends ResourceCondition {
SchemaAvailableCondition() {
super("ValidationSchema",
"hapi.fhir.validation",
"schema-location",
"classpath:/org/hl7/fhir/instance/model/schema",
"classpath:/org/hl7/fhir/dstu2016may/model/schema",
"classpath:/org/hl7/fhir/dstu3/model/schema");
}
}
}
@Configuration
@ConditionalOnProperty("hapi.fhir.server.url")
@EnableConfigurationProperties(FhirProperties.class)
static class FhirRestfulClientConfiguration {
private final FhirProperties properties;
private final List<IClientInterceptor> clientInterceptors;
public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider<List<IClientInterceptor>> clientInterceptors) {
this.properties = properties;
this.clientInterceptors = clientInterceptors.getIfAvailable();
}
@Bean
@ConditionalOnBean(IRestfulClientFactory.class)
public IGenericClient fhirClient(final IRestfulClientFactory clientFactory) {
IGenericClient fhirClient = clientFactory.newGenericClient(this.properties.getServer().getUrl());
if (!CollectionUtils.isEmpty(this.clientInterceptors)) {
for (IClientInterceptor interceptor : this.clientInterceptors) {
fhirClient.registerInterceptor(interceptor);
}
}
return fhirClient;
}
@Configuration
@ConditionalOnClass(HttpClient.class)
@ConditionalOnMissingClass("okhttp3.OkHttpClient")
static class Apache {
private final FhirContext context;
public Apache(FhirContext context) {
this.context = context;
}
@Bean
@ConditionalOnMissingBean
@ConfigurationProperties("hapi.fhir.rest.client.apache")
public IRestfulClientFactory fhirRestfulClientFactory() {
ApacheRestfulClientFactory restfulClientFactory = new ApacheRestfulClientFactory(this.context);
return restfulClientFactory;
}
}
@Configuration
@ConditionalOnClass(OkHttpClient.class)
static class OkHttp {
private final FhirContext context;
public OkHttp(FhirContext context) {
this.context = context;
}
@Bean
@ConditionalOnMissingBean
@ConfigurationProperties("hapi.fhir.rest.client.okhttp")
public IRestfulClientFactory fhirRestfulClientFactory() {
OkHttpRestfulClientFactory restfulClientFactory = new OkHttpRestfulClientFactory(this.context);
return restfulClientFactory;
}
}
}
}

View File

@ -0,0 +1,85 @@
package ca.uhn.fhir.spring.boot.autoconfigure;
import ca.uhn.fhir.context.FhirVersionEnum;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hapi.fhir")
public class FhirProperties {
private FhirVersionEnum version = FhirVersionEnum.DSTU2;
private Server server = new Server();
private Validation validation = new Validation();
public FhirVersionEnum getVersion() {
return version;
}
public void setVersion(FhirVersionEnum version) {
this.version = version;
}
public Server getServer() {
return server;
}
public void setServer(Server server) {
this.server = server;
}
public Validation getValidation() {
return validation;
}
public void setValidation(Validation validation) {
this.validation = validation;
}
public static class Server {
private String url;
private String path = "/fhir/*";
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
public static class Validation {
private boolean enabled = true;
private boolean requestOnly = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isRequestOnly() {
return requestOnly;
}
public void setRequestOnly(boolean requestOnly) {
this.requestOnly = requestOnly;
}
}
}

View File

@ -0,0 +1,13 @@
package ca.uhn.fhir.spring.boot.autoconfigure;
import ca.uhn.fhir.rest.server.RestfulServer;
@FunctionalInterface
public interface FhirRestfulServerCustomizer {
/**
* Customize the server.
* @param server the server to customize
*/
void customize(RestfulServer server);
}

View File

@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=ca.uhn.fhir.spring.boot.autoconfigure.FhirAutoConfiguration

View File

@ -0,0 +1,171 @@
package ca.uhn.fhir.spring.boot.autoconfigure;
import java.net.URL;
import java.net.URLClassLoader;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory;
import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.spring.boot.autoconfigure.FhirAutoConfiguration.FhirJpaServerConfiguration.Dstu3;
import org.assertj.core.util.Arrays;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link FhirAutoConfiguration}.
*
* @author Mathieu Ouellet
*/
public class FhirAutoConfigurationTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private AnnotationConfigApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void withFhirContext() throws Exception {
load();
assertThat(this.context.getBeansOfType(FhirContext.class)).hasSize(1);
}
@Test
public void withFhirVersion() throws Exception {
load("hapi.fhir.version:DSTU3");
assertThat(this.context.getBean(FhirContext.class).getVersion()).isEqualTo(FhirVersionEnum.DSTU3.getVersionImplementation());
}
@Test
public void withRestfulServer() {
load("hapi.fhir.server.path:/hapi-fhir/*");
assertThat(this.context.getBeansOfType(ServletRegistrationBean.class)).hasSize(1);
assertThat(this.context.getBeansOfType(RestfulServer.class)).hasSize(1);
assertThat(this.context.getBean(ServletRegistrationBean.class).getUrlMappings()).contains("/hapi-fhir/*");
}
@Test
public void withJpaServer() {
load(
Arrays.array(
EmbeddedDataSourceConfiguration.class,
HibernateJpaAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
FhirAutoConfiguration.class),
"hapi.fhir.version:DSTU3",
"spring.jpa.properties.hibernate.search.default.indexBase:target/lucenefiles",
"spring.jpa.properties.hibernate.search.model_mapping:ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory");
assertThat(this.context.getBeansOfType(DaoConfig.class)).hasSize(1);
assertThat(this.context.getBeansOfType(Dstu3.class)).hasSize(1);
}
@Test
public void withNoValidation() {
load("hapi.fhir.validation.enabled:false");
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(RequestValidatingInterceptor.class);
}
@Test
public void withValidation() {
load();
assertThat(this.context.getBeansOfType(IServerInterceptor.class)).hasSize(1);
}
@Test
public void withValidations() {
load("hapi.fhir.validation.request-only=false");
assertThat(this.context.getBeansOfType(IServerInterceptor.class)).hasSize(2);
}
@Test
public void withCustomValidationSchemaLocation() {
load("hapi.fhir.validation.schema-location:custom-schema-location");
assertThat(this.context.getBeansOfType(IServerInterceptor.class)).hasSize(1);
}
@Test
public void withApacheHttpClient() {
load(new HidePackagesClassLoader("okhttp3"), "hapi.fhir.server.url:http://localhost:8080");
assertThat(this.context.getBeansOfType(ApacheRestfulClientFactory.class)).hasSize(1);
assertThat(this.context.getBeansOfType(OkHttpRestfulClientFactory.class)).hasSize(0);
}
@Test
public void withOkHttpClient() {
load("hapi.fhir.server.url:http://localhost:8080");
assertThat(this.context.getBeansOfType(OkHttpRestfulClientFactory.class)).hasSize(1);
assertThat(this.context.getBeansOfType(ApacheRestfulClientFactory.class)).hasSize(0);
}
private void load(String... environment) {
load(new Class<?>[] { FhirAutoConfiguration.class }, null, environment);
}
private void load(ClassLoader classLoader, String... environment) {
load(new Class<?>[] { FhirAutoConfiguration.class }, classLoader, environment);
}
private void load(Class<?>[] configs, String... environment) {
load(configs, null, environment);
}
private void load(Class<?>[] configs, ClassLoader classLoader, String... environment) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(applicationContext, environment);
if (classLoader != null) {
applicationContext.setClassLoader(classLoader);
}
if (configs != null) {
applicationContext.register(configs);
}
applicationContext.refresh();
this.context = applicationContext;
}
private static final class HidePackagesClassLoader extends URLClassLoader {
private final String[] hiddenPackages;
private HidePackagesClassLoader(String... hiddenPackages) {
super(new URL[0], FhirAutoConfigurationTest.class.getClassLoader());
this.hiddenPackages = hiddenPackages;
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
for (String hiddenPackage : this.hiddenPackages) {
if (name.startsWith(hiddenPackage)) {
throw new ClassNotFoundException();
}
}
return super.loadClass(name, resolve);
}
}
}

View File

@ -0,0 +1,13 @@
spring:
jpa:
hibernate:
ddl-auto: update
properties:
hibernate.jdbc.batch_size: 20
hibernate.cache.use_query_cache: false
hibernate.cache.use_second_level_cache: false
hibernate.cache.use_structured_entries: false
hibernate.cache.use_minimal_puts: false
hibernate.search.default.directory_provider: filesystem
hibernate.search.default.indexBase: target/lucenefiles
hibernate.search.lucene_version: LUCENE_CURRENT

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
</configuration>

View File

@ -0,0 +1,64 @@
<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>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Optional -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,37 @@
package sample.fhir.client;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import org.hl7.fhir.dstu3.model.CapabilityStatement;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SampleApacheRestfulClientApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApacheRestfulClientApplication.class, args);
}
@Bean
public LoggingInterceptor loggingInterceptor() {
return new LoggingInterceptor(true);
}
@Bean
public CommandLineRunner runner(final IGenericClient fhirClient) {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
fhirClient.capabilities()
.ofType(CapabilityStatement.class)
.execute();
}
};
}
}

View File

@ -0,0 +1,13 @@
server:
port: 8888
hapi:
fhir:
version: dstu3
server:
url: http://localhost:8080/fhir
management:
security:
enabled: false
logging:
level:
ca.uhn.fhir.jaxrs: debug

View File

@ -0,0 +1,64 @@
<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>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client-okhttp</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Optional -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,37 @@
package sample.fhir.client;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import org.hl7.fhir.dstu3.model.CapabilityStatement;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SampleOkHttpRestfulClientApplication {
public static void main(String[] args) {
SpringApplication.run(SampleOkHttpRestfulClientApplication.class, args);
}
@Bean
public LoggingInterceptor loggingInterceptor() {
return new LoggingInterceptor(true);
}
@Bean
public CommandLineRunner runner(final IGenericClient fhirClient) {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
fhirClient.capabilities()
.ofType(CapabilityStatement.class)
.execute();
}
};
}
}

View File

@ -0,0 +1,13 @@
server:
port: 8888
hapi:
fhir:
version: dstu3
server:
url: http://localhost:8080/fhir
management:
security:
enabled: false
logging:
level:
ca.uhn.fhir.jaxrs: debug

View File

@ -0,0 +1,67 @@
<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>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jaxrsserver-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu3</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Optional -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,20 @@
package sample.fhir.server.jersey;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SampleJerseyRestfulServerApplication {
public static void main(String[] args) {
SpringApplication.run(SampleJerseyRestfulServerApplication.class, args);
}
@Bean
public LoggingInterceptor loggingInterceptor() {
return new LoggingInterceptor();
}
}

View File

@ -0,0 +1,73 @@
package sample.fhir.server.jersey.provider;
import java.util.concurrent.ConcurrentHashMap;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Patient;
import org.springframework.stereotype.Component;
@Component
public class PatientResourceProvider extends AbstractJaxRsResourceProvider<Patient> {
private static Long counter = 1L;
private static final ConcurrentHashMap<String, Patient> patients = new ConcurrentHashMap<>();
static {
patients.put(String.valueOf(counter), createPatient("Van Houte"));
patients.put(String.valueOf(counter), createPatient("Agnew"));
for (int i = 0; i < 20; i++) {
patients.put(String.valueOf(counter), createPatient("Random Patient " + counter));
}
}
public PatientResourceProvider(FhirContext fhirContext) {
super(fhirContext);
}
@Read
public Patient find(@IdParam final IdType theId) {
if (patients.containsKey(theId.getIdPart())) {
return patients.get(theId.getIdPart());
} else {
throw new ResourceNotFoundException(theId);
}
}
@Create
public MethodOutcome createPatient(@ResourceParam Patient patient) {
patient.setId(createId(counter, 1L));
patients.put(String.valueOf(counter), patient);
return new MethodOutcome(patient.getIdElement());
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
private static IdType createId(final Long id, final Long theVersionId) {
return new IdType("Patient", "" + id, "" + theVersionId);
}
private static Patient createPatient(final String name) {
final Patient patient = new Patient();
patient.getName().add(new HumanName().setFamily(name));
patient.setId(createId(counter, 1L));
counter++;
return patient;
}
}

View File

@ -0,0 +1,21 @@
hapi:
fhir:
version: dstu3
server:
path: /fhir/*
rest:
server-name: hapi-fhir-spring-boot-sample-server-jersey
server-version: 1.0.0
implementation-description: Spring Boot Jersey Server Sample
default-response-encoding: json
e-tag-support: enabled
default-pretty-print: true
validation:
enabled: true
request-only: true
management:
security:
enabled: false
logging:
level:
ca.uhn.fhir.jaxrs: debug

View File

@ -0,0 +1,41 @@
package sample.fhir.server.jersey;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleJerseyRestfulServerApplicationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void metadata() {
ResponseEntity<String> entity = this.restTemplate.getForEntity(
"/fhir/metadata",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\": \"active\"");
}
@Test
public void patientResource() {
ResponseEntity<String> entity = this.restTemplate.getForEntity(
"/fhir/Patient/1",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"family\": \"Van Houte\"");
}
}

View File

@ -0,0 +1,75 @@
<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>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-server-jpa</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jaxrsserver-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- Optional -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,12 @@
package sample.fhir.server.jpa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleJpaRestfulServerApplication {
public static void main(String[] args) {
SpringApplication.run(SampleJpaRestfulServerApplication.class, args);
}
}

View File

@ -0,0 +1,39 @@
spring:
jpa:
hibernate:
ddl-auto: create-drop
properties:
hibernate.jdbc.batch_size: 20
hibernate.cache.use_query_cache: false
hibernate.cache.use_second_level_cache: false
hibernate.cache.use_structured_entries: false
hibernate.cache.use_minimal_puts: false
hibernate.search.default.directory_provider: filesystem
hibernate.search.default.indexBase: target/lucenefiles
hibernate.search.lucene_version: LUCENE_CURRENT
hibernate.search.model_mapping: ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory
h2:
console:
enabled: true
hapi:
fhir:
version: dstu3
server:
path: /fhir/*
rest:
server-name: hapi-fhir-spring-boot-sample-server-jpa
server-version: 1.0.0
implementation-description: Spring Boot Jpa Server Sample
default-response-encoding: json
e-tag-support: enabled
default-pretty-print: true
validation:
enabled: true
request-only: true
jpa:
scheduling-disabled: true
subscription-enabled: false
management:
security:
enabled: false

View File

@ -0,0 +1,41 @@
package sample.fhir.server.jpa;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleJpaRestfulServerApplicationTest {
@Autowired
FhirContext fhirContext;
@LocalServerPort
int port;
@Test
public void createAndRead() {
IGenericClient client = fhirContext.newRestfulGenericClient("http://localhost:" + port + "/fhir");
Patient patient = new Patient();
patient.addName().setFamily("Test");
IIdType id = client.create().resource(patient).execute().getId();
System.out.println(id);
Patient result = client.read().resource(Patient.class).withId(id).execute();
System.out.println(result);
}
}

View File

@ -0,0 +1,21 @@
<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>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<packaging>pom</packaging>
<modules>
<module>hapi-fhir-spring-boot-sample-client-apache</module>
<module>hapi-fhir-spring-boot-sample-client-okhttp</module>
<module>hapi-fhir-spring-boot-sample-server-jersey</module>
<module>hapi-fhir-spring-boot-sample-server-jpa</module>
</modules>
</project>

View File

@ -0,0 +1,27 @@
<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>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,21 @@
<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>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>3.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hapi-fhir-spring-boot</artifactId>
<packaging>pom</packaging>
<modules>
<module>hapi-fhir-spring-boot-autoconfigure</module>
<module>hapi-fhir-spring-boot-starter</module>
<module>hapi-fhir-spring-boot-samples</module>
</modules>
</project>

View File

@ -1499,7 +1499,7 @@ public class JsonParserDstu2_1Test {
String val = ourCtx.newJsonParser().encodeResourceToString(patient); String val = ourCtx.newJsonParser().encodeResourceToString(patient);
String expected = "{\"resourceType\":\"Binary\",\"id\":\"11\",\"contentType\":\"foo\",\"content\":\"AQIDBA==\"}"; String expected = "{\"resourceType\":\"Binary\",\"id\":\"11\",\"meta\":{\"versionId\":\"22\"},\"contentType\":\"foo\",\"content\":\"AQIDBA==\"}";
ourLog.info("Expected: {}", expected); ourLog.info("Expected: {}", expected);
ourLog.info("Actual : {}", val); ourLog.info("Actual : {}", val);
assertEquals(expected, val); assertEquals(expected, val);

View File

@ -70,13 +70,6 @@
<skipUpdateLicense>true</skipUpdateLicense> <skipUpdateLicense>true</skipUpdateLicense>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>

View File

@ -1,13 +1,8 @@
package ca.uhn.fhir.context; package ca.uhn.fhir.context;
import static org.junit.Assert.*; import ca.uhn.fhir.rest.client.MyPatientWithExtensions;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import java.util.ArrayList; import ca.uhn.fhir.util.TestUtil;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Reference; import org.hl7.fhir.dstu3.model.Reference;
@ -15,19 +10,21 @@ import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.rest.client.MyPatientWithExtensions; import java.util.ArrayList;
import ca.uhn.fhir.util.TestUtil; import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.*;
public class FhirContextDstu3Test { public class FhirContextDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContextDstu3Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContextDstu3Test.class);
private static FhirContext ourCtx = FhirContext.forDstu3(); private static FhirContext ourCtx = FhirContext.forDstu3();
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Test @Test
public void testAutoDetectVersion() { public void testAutoDetectVersion() {
@ -35,6 +32,17 @@ public class FhirContextDstu3Test {
assertEquals(FhirVersionEnum.DSTU3, ctx.getVersion().getVersion()); assertEquals(FhirVersionEnum.DSTU3, ctx.getVersion().getVersion());
} }
@Test
public void testCustomTypeDoesntBecomeDefault() {
FhirContext ctx = FhirContext.forDstu3();
MyPatientWithExtensions pt = new MyPatientWithExtensions();
pt.addName().addGiven("FOO");
ctx.newXmlParser().encodeResourceToString(pt);
assertEquals(Patient.class, ctx.getResourceDefinition("Patient").getImplementingClass());
}
/** /**
* See #344 * See #344
*/ */
@ -44,7 +52,7 @@ public class FhirContextDstu3Test {
assertEquals(Reference.class, ourCtx.getElementDefinition("Reference").getImplementingClass()); assertEquals(Reference.class, ourCtx.getElementDefinition("Reference").getImplementingClass());
assertEquals(Reference.class, ourCtx.getElementDefinition("REFerence").getImplementingClass()); assertEquals(Reference.class, ourCtx.getElementDefinition("REFerence").getImplementingClass());
} }
/** /**
* See #344 * See #344
*/ */
@ -57,73 +65,92 @@ public class FhirContextDstu3Test {
} }
@Test @Test
public void testCustomTypeDoesntBecomeDefault() { public void testInitialisationThreadSafety() {
FhirContext ctx = FhirContext.forDstu3(); final FhirContext ctx = FhirContext.forDstu3();
MyPatientWithExtensions pt = new MyPatientWithExtensions(); final int numThreads = 40;
pt.addName().addGiven("FOO"); final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<Throwable>());
ctx.newXmlParser().encodeResourceToString(pt); final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
try {
assertEquals(Patient.class, ctx.getResourceDefinition("Patient").getImplementingClass()); final CountDownLatch threadsReady = new CountDownLatch(numThreads);
final CountDownLatch threadsFinished = new CountDownLatch(numThreads);
for (int i = 0; i < numThreads; i++) {
threadPool.submit(
new Runnable() {
public void run() {
threadsReady.countDown();
try {
threadsReady.await();
RuntimeResourceDefinition def = ctx.getResourceDefinition("patient");
ourLog.info(def.toString());
assertNotNull(def);
} catch (final Exception e) {
exceptions.add(e);
}
threadsFinished.countDown();
}
}
);
}
threadsFinished.await();
} catch (final InterruptedException e) {
exceptions.add(e);
} finally {
threadPool.shutdownNow();
}
assertTrue("failed with exception(s): " + exceptions, exceptions.isEmpty());
} }
/**
* See #794
*/
@Test
public void testInitializeThreadSafety2() throws InterruptedException {
final FhirContext dstu3FhirContext = FhirContext.forDstu3();
final AtomicInteger count = new AtomicInteger();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
OperationOutcomeUtil.newInstance(dstu3FhirContext);
ourLog.info("Have finished {}", count.incrementAndGet());
}
}).start();
}
TestUtil.waitForSize(10, count);
}
@Test @Test
public void testQueryBoundCode() { public void testQueryBoundCode() {
RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class); RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class);
String childName = "gender"; String childName = "gender";
BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName); BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName);
ourLog.trace(genderChild.getClass().getName()); ourLog.trace(genderChild.getClass().getName());
assertEquals(AdministrativeGender.class, genderChild.getBoundEnumType()); assertEquals(AdministrativeGender.class, genderChild.getBoundEnumType());
} }
@Test @Test
public void testQueryNonBoundCode() { public void testQueryNonBoundCode() {
RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class); RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class);
String childName = "name"; String childName = "name";
BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName); BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName);
ourLog.trace(genderChild.getClass().getName()); ourLog.trace(genderChild.getClass().getName());
assertEquals(null, genderChild.getBoundEnumType()); assertEquals(null, genderChild.getBoundEnumType());
} }
@Test @AfterClass
public void testInitialisationThreadSafety() { public static void afterClassClearContext() {
final FhirContext ctx = FhirContext.forDstu3(); TestUtil.clearAllStaticFieldsForUnitTest();
}
final int numThreads = 40;
final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<Throwable>());
final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
try {
final CountDownLatch threadsReady = new CountDownLatch(numThreads);
final CountDownLatch threadsFinished = new CountDownLatch(numThreads);
for (int i = 0; i < numThreads; i++) {
threadPool.submit(
new Runnable() {
public void run() {
threadsReady.countDown();
try {
threadsReady.await();
RuntimeResourceDefinition def = ctx.getResourceDefinition("patient");
ourLog.info(def.toString());
assertNotNull(def);
} catch(final Exception e) {
exceptions.add(e);
}
threadsFinished.countDown();
}
}
);
}
threadsFinished.await();
} catch(final InterruptedException e) {
exceptions.add(e);
} finally {
threadPool.shutdownNow();
}
assertTrue("failed with exception(s): " + exceptions, exceptions.isEmpty());
}
} }

View File

@ -1,32 +1,5 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.*;
import org.junit.*;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
@ -38,15 +11,46 @@ import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.*; import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
public class SearchR4Test { public class SearchR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchR4Test.class);
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4(); private static FhirContext ourCtx = FhirContext.forR4();
private static TokenAndListParam ourIdentifiers; private static TokenAndListParam ourIdentifiers;
private static String ourLastMethod; private static String ourLastMethod;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchR4Test.class);
private static int ourPort; private static int ourPort;
private static Server ourServer; private static Server ourServer;
@ -57,74 +61,63 @@ public class SearchR4Test {
ourIdentifiers = null; ourIdentifiers = null;
} }
@Test private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException {
public void testSearchNormal() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
CloseableHttpResponse status = ourClient.execute(httpGet); CloseableHttpResponse status = ourClient.execute(httpGet);
Bundle bundle;
try { try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent); ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
assertEquals("search", ourLastMethod); assertEquals(theExpectEncoding, ct);
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem()); assertEquals(10, bundle.getEntry().size());
assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertNotNull(linkNext);
} finally { } finally {
IOUtils.closeQuietly(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
} }
return bundle;
} }
@Test @Test
public void testSearchWithInvalidChain() throws Exception { public void testPagingPreservesElements() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar");
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent);
assertEquals(
"Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)",
oo.getIssueFirstRep().getDiagnostics());
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@Test
public void testPagingPreservesEncodingJson() throws Exception {
HttpGet httpGet; HttpGet httpGet;
String linkNext; String linkNext;
Bundle bundle; Bundle bundle;
String linkSelf;
// Initial search // Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_elements=name");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
assertThat(linkSelf, containsString("_elements=name"));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json")); assertThat(linkNext, containsString("_elements=name"));
ourLog.info(toJson(bundle));
// Fetch the next page // Fetch the next page
httpGet = new HttpGet(linkNext); httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json")); assertThat(linkNext, containsString("_elements=name"));
// Fetch the next page // Fetch the next page
httpGet = new HttpGet(linkNext); httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json")); assertThat(linkNext, containsString("_elements=name"));
// Fetch the next page // Fetch the next page
httpGet = new HttpGet(linkNext); httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json")); assertThat(linkNext, containsString("_elements=name"));
} }
@ -161,34 +154,35 @@ public class SearchR4Test {
} }
@Test @Test
public void testPagingPreservesEncodingXml() throws Exception { public void testPagingPreservesEncodingJson() throws Exception {
HttpGet httpGet; HttpGet httpGet;
String linkNext; String linkNext;
Bundle bundle; Bundle bundle;
// Initial search // Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), containsString("active"));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml")); assertThat(linkNext, containsString("_format=json"));
// Fetch the next page // Fetch the next page
httpGet = new HttpGet(linkNext); httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml")); assertThat(linkNext, containsString("_format=json"));
// Fetch the next page // Fetch the next page
httpGet = new HttpGet(linkNext); httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml")); assertThat(linkNext, containsString("_format=json"));
// Fetch the next page // Fetch the next page
httpGet = new HttpGet(linkNext); httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml")); assertThat(linkNext, containsString("_format=json"));
} }
@ -260,26 +254,76 @@ public class SearchR4Test {
} }
private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException { @Test
CloseableHttpResponse status = ourClient.execute(httpGet); public void testPagingPreservesEncodingXml() throws Exception {
HttpGet httpGet;
String linkNext;
Bundle bundle; Bundle bundle;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
}
@Test
public void testSearchNormal() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
CloseableHttpResponse status = ourClient.execute(httpGet);
try { try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent); ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
assertEquals(theExpectEncoding, ct); assertEquals("search", ourLastMethod);
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
assertEquals(10, bundle.getEntry().size()); assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem());
String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
assertNotNull(linkNext);
} finally { } finally {
IOUtils.closeQuietly(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
} }
return bundle;
}
@Test
public void testSearchWithInvalidChain() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar");
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent);
assertEquals(
"Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)",
oo.getIssueFirstRep().getDiagnostics());
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
} }
@Test @Test
public void testSearchWithPostAndInvalidParameters() throws Exception { public void testSearchWithPostAndInvalidParameters() throws Exception {
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort); IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
@ -293,14 +337,14 @@ public class SearchR4Test {
client.registerInterceptor(interceptor); client.registerInterceptor(interceptor);
try { try {
client client
.search() .search()
.forResource(Patient.class) .forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar")) .where(new StringClientParam("foo").matches().value("bar"))
.prettyPrint() .prettyPrint()
.usingStyle(SearchStyleEnum.POST) .usingStyle(SearchStyleEnum.POST)
.returnBundle(org.hl7.fhir.r4.model.Bundle.class) .returnBundle(org.hl7.fhir.r4.model.Bundle.class)
.encodedJson() .encodedJson()
.execute(); .execute();
fail(); fail();
} catch (InvalidRequestException e) { } catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Invalid request: The FHIR endpoint on this server does not know how to handle POST operation[Patient/_search] with parameters [[_pretty, foo]]")); assertThat(e.getMessage(), containsString("Invalid request: The FHIR endpoint on this server does not know how to handle POST operation[Patient/_search] with parameters [[_pretty, foo]]"));
@ -308,6 +352,10 @@ public class SearchR4Test {
} }
private String toJson(Bundle theBundle) {
return ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theBundle);
}
@AfterClass @AfterClass
public static void afterClassClearContext() throws Exception { public static void afterClassClearContext() throws Exception {
ourServer.stop(); ourServer.stop();
@ -349,16 +397,17 @@ public class SearchR4Test {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Search() @Search()
public List search( public List search(
@RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) { @RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
ourLastMethod = "search"; ourLastMethod = "search";
ourIdentifiers = theIdentifiers; ourIdentifiers = theIdentifiers;
ArrayList<Patient> retVal = new ArrayList<Patient>(); ArrayList<Patient> retVal = new ArrayList<Patient>();
for (int i = 0; i < 200; i++) { for (int i = 0; i < 200; i++) {
Patient patient = new Patient(); Patient patient = new Patient();
patient.addName(new HumanName().setFamily("FAMILY")); patient.addName(new HumanName().setFamily("FAMILY"));
patient.setActive(true);
patient.getIdElement().setValue("Patient/" + i); patient.getIdElement().setValue("Patient/" + i);
retVal.add((Patient) patient); retVal.add(patient);
} }
return retVal; return retVal;
} }

23
pom.xml
View File

@ -370,6 +370,14 @@
<id>malcolmm83</id> <id>malcolmm83</id>
<name>Malcolm McRoberts</name> <name>Malcolm McRoberts</name>
</developer> </developer>
<developer>
<id>mouellet</id>
<name>Mathieu Ouellet</name>
</developer>
<developer>
<id>JiajingLiang</id>
<name>Jiajing Liang</name>
</developer>
</developers> </developers>
<licenses> <licenses>
@ -403,6 +411,7 @@
<phloc_schematron_version>2.7.1</phloc_schematron_version> <phloc_schematron_version>2.7.1</phloc_schematron_version>
<phloc_commons_version>4.4.11</phloc_commons_version> <phloc_commons_version>4.4.11</phloc_commons_version>
<spring_version>5.0.0.RELEASE</spring_version> <spring_version>5.0.0.RELEASE</spring_version>
<spring-boot.version>1.5.6.RELEASE</spring-boot.version>
<thymeleaf-version>3.0.7.RELEASE</thymeleaf-version> <thymeleaf-version>3.0.7.RELEASE</thymeleaf-version>
<!-- We are aiming to still work on a very old version of SLF4j even though we depend on the newest, just to be nice to users of the API. This version is tested in the hapi-fhir-cobertura. --> <!-- We are aiming to still work on a very old version of SLF4j even though we depend on the newest, just to be nice to users of the API. This version is tested in the hapi-fhir-cobertura. -->
@ -970,6 +979,13 @@
<artifactId>spring-websocket</artifactId> <artifactId>spring-websocket</artifactId>
<version>${spring_version}</version> <version>${spring_version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency> <dependency>
<groupId>org.thymeleaf</groupId> <groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId> <artifactId>thymeleaf</artifactId>
@ -1032,7 +1048,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.6.2</version> <version>3.7.0</version>
<configuration> <configuration>
<source>1.7</source> <source>1.7</source>
<target>1.7</target> <target>1.7</target>
@ -1045,6 +1061,10 @@
<testTarget>1.8</testTarget> <testTarget>1.8</testTarget>
<forceJavacCompilerUse>true</forceJavacCompilerUse> <forceJavacCompilerUse>true</forceJavacCompilerUse>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<fork>true</fork>
<meminitial>128m</meminitial>
<maxmem>1600m</maxmem>
</configuration> </configuration>
<dependencies> <dependencies>
<dependency> <dependency>
@ -1880,6 +1900,7 @@
<module>example-projects/hapi-fhir-standalone-overlay-example</module> <module>example-projects/hapi-fhir-standalone-overlay-example</module>
<module>hapi-fhir-jacoco</module> <module>hapi-fhir-jacoco</module>
<module>hapi-fhir-igpacks</module> <module>hapi-fhir-igpacks</module>
<module>hapi-fhir-spring-boot</module>
<!--<module>hapi-fhir-osgi-core</module>--> <!--<module>hapi-fhir-osgi-core</module>-->
</modules> </modules>
</profile> </profile>

View File

@ -154,7 +154,7 @@
JAX-RS client framework now supports the ability to JAX-RS client framework now supports the ability to
register your own JAX-RS Component Classes against the client, register your own JAX-RS Component Classes against the client,
as well as better documentation about thread safety. Thanks as well as better documentation about thread safety. Thanks
to Sébastien Rivière for the pull request! to Sébastien Rivière for the pull request!
</action> </action>
<action type="fix" issue="717"> <action type="fix" issue="717">
Processing of the If-Modified-Since header on FHIR read operations was reversed, Processing of the If-Modified-Since header on FHIR read operations was reversed,
@ -175,11 +175,43 @@
was not encoded correctly. Thanks to Malcolm McRoberts for the pull was not encoded correctly. Thanks to Malcolm McRoberts for the pull
request with fix and test case! request with fix and test case!
</action> </action>
<action type="add">
Bundle resources did not have their version encoded when serializing
in FHIR resource (XML/JSON) format.
</action>
<action type="add"> <action type="add">
The Binary resource endpoint now supports the `X-Security-Context` header when The Binary resource endpoint now supports the `X-Security-Context` header when
reading or writing Binary contents using their native Content-Type (i.e exchanging reading or writing Binary contents using their native Content-Type (i.e exchanging
the raw binary with the server, as opposed to exchanging a FHIR resource). the raw binary with the server, as opposed to exchanging a FHIR resource).
</action> </action>
<action type="fix">
When paging through multiple pages of search results, if the
client had requested a subset of resources to be returned using the
<![CDATA[<code>_elements</code>]]> parameter, the elements list
was lost after the first page of results.
In addition, elements will not remove elements from
search/history Bundles (i.e. elements from the Bundle itself, as opposed
to elements in the entry resources) unless the Bundle elements are
explicitly listed, e.g. <![CDATA[<code>_include=Bundle.total</code>]]>.
Thanks to @parisni for reporting!
</action>
<action type="add" issue="743">
Add support for Spring Boot for initializing a number of parts of the library,
as well as several examples.
Thanks to Mathieu Ouellet for the contribution!
</action>
<action type="add" issue="747">
JPA server now has lucene index support moved to separate classes from the entity
classes in order to facilitate support for ElasticSearch. Thanks to Jiang Liang
for the pull request!
</action>
<action type="add" issue="755">
A new client interceptor has been added called
AdditionalRequestHeadersInterceptor, which allows
a developer to add additional custom headers to a
client requests.
Thanks to Clayton Bodendein for the pull request!
</action>
</release> </release>
<release version="3.0.0" date="2017-09-27"> <release version="3.0.0" date="2017-09-27">
<action type="add"> <action type="add">
@ -545,7 +577,7 @@ Bundle bundle = client.search().forResource(Patient.class)
<action type="fix" issue="686"> <action type="fix" issue="686">
Correct an issue in the validator (DSTU3/R4) where elements were not always Correct an issue in the validator (DSTU3/R4) where elements were not always
correctly validated if the element contained only a profiled extension. Thanks correctly validated if the element contained only a profiled extension. Thanks
to Sébastien Rivière for the pull request! to Sébastien Rivière for the pull request!
</action> </action>
<action type="add" issue="701"> <action type="add" issue="701">
Testing UI now has a dropdown for modifiers on token search. Thanks Testing UI now has a dropdown for modifiers on token search. Thanks
@ -559,7 +591,7 @@ Bundle bundle = client.search().forResource(Patient.class)
</action> </action>
<action type="fix" issue="695"> <action type="fix" issue="695">
Extensions on ID datatypes were not parsed or serialized correctly. Thanks to Extensions on ID datatypes were not parsed or serialized correctly. Thanks to
Stephen Rivière for the pull request! Stephen Rivière for the pull request!
</action> </action>
<action type="fix" issue="710"> <action type="fix" issue="710">
Fix a bug in REST Hook Subscription interceptors which prevented subscriptions Fix a bug in REST Hook Subscription interceptors which prevented subscriptions
@ -880,7 +912,7 @@ Bundle bundle = client.search().forResource(Patient.class)
<action type="add"> <action type="add">
DaoConfig#setAllowInlineMatchUrlReferences() now defaults to DaoConfig#setAllowInlineMatchUrlReferences() now defaults to
<![CDATA[<code>true</code>]]> since inline conditional references <![CDATA[<code>true</code>]]> since inline conditional references
are now a part of the FHIR specification. Thanks to Jan Dědek for are now a part of the FHIR specification. Thanks to Jan Dědek for
pointing this out! pointing this out!
</action> </action>
<action type="add" issue="609"> <action type="add" issue="609">
@ -1860,7 +1892,7 @@ Bundle bundle = client.search().forResource(Patient.class)
</action> </action>
<action type="fix" issue="426"> <action type="fix" issue="426">
Parser failed to parse resources containing an extension with a value type of Parser failed to parse resources containing an extension with a value type of
"id". Thanks to Raphael Mäder for reporting! "id". Thanks to Raphael Mäder for reporting!
</action> </action>
<action type="fix"> <action type="fix">
When committing a transaction in JPA server When committing a transaction in JPA server
@ -2122,7 +2154,7 @@ Bundle bundle = client.search().forResource(Patient.class)
array instead of a string. Thanks to David Hay for reporting! array instead of a string. Thanks to David Hay for reporting!
</action> </action>
<action type="add" issue="367"> <action type="add" issue="367">
Sébastien Rivière contributed an excellent pull request which adds a Sébastien Rivière contributed an excellent pull request which adds a
number of enhancements to JAX-RS module: number of enhancements to JAX-RS module:
<![CDATA[ <![CDATA[
<ul> <ul>