[(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
**/.project
**/.classpath
**/.factorypath
**/.springBeans
# 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>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
</dependencies>
<reporting>

View File

@ -116,10 +116,10 @@
<artifactId>spring-jcl</artifactId>
</dependency>
</ignoredDependencies>
<ignoredResources>
<ignoredResource>changelog.txt</ignoredResource>
<ignoredResourcePatterns>
<ignoredResourcePattern>changelog.txt</ignoredResourcePattern>
<ignoredResource>javac.bat</ignoredResource>
</ignoredResources>
</ignoredResourcePatterns>
</configuration>
</plugin>
</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 ContainedResources myContainedResources;
private boolean myEncodeElementsAppliesToChildResourcesOnly;
private FhirContext myContext;
private Set<String> myDontEncodeElements;
private boolean myDontEncodeElementsIncludesStars;
@ -556,6 +556,16 @@ public abstract class BaseParser implements IParser {
&& theIncludedResource == false;
}
@Override
public boolean isEncodeElementsAppliesToChildResourcesOnly() {
return myEncodeElementsAppliesToChildResourcesOnly;
}
@Override
public void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly) {
myEncodeElementsAppliesToChildResourcesOnly = theEncodeElementsAppliesToChildResourcesOnly;
}
@Override
public boolean isOmitResourceId() {
return myOmitResourceId;
@ -1051,7 +1061,13 @@ public abstract class BaseParser implements IParser {
}
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) {
@ -1070,6 +1086,9 @@ public abstract class BaseParser implements IParser {
} else {
thePathBuilder.append(myResDef.getName());
}
if (theElements == null) {
return true;
}
if (theElements.contains(thePathBuilder.toString())) {
return true;
}

View File

@ -206,6 +206,22 @@ public interface IParser {
*/
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
* 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) {
RuntimeResourceDefinition def = theCtx.getResourceDefinition("Binary");
BaseRuntimeChildDefinition child = def.getChildByName("securityContext");
List<IBase> values = child.getAccessor().getValues(theBinary);
IBaseReference retVal = null;
if (values.size() > 0) {
retVal = (IBaseReference) values.get(0);
if (child != null) {
List<IBase> values = child.getAccessor().getValues(theBinary);
if (values.size() > 0) {
retVal = (IBaseReference) values.get(0);
}
}
return retVal;
}

View File

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

View File

@ -30,8 +30,10 @@ import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
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());
}
/**
* <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>
* <p>

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.demo;
import java.util.Properties;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.springframework.context.annotation.Bean;
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_structured_entries", "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.indexBase", "target/lucenefiles");
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
* 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 {

View File

@ -26,20 +26,8 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.builder.ToStringBuilder;
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.search.annotations.*;
import org.hibernate.search.annotations.Parameter;
import javax.persistence.*;
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_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
public class ResourceTable extends BaseHasResource implements Serializable {
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.ToStringBuilder;
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.Analyzer;
import org.hibernate.search.annotations.AnalyzerDef;
import org.hibernate.search.annotations.AnalyzerDefs;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.Indexed;
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.search.DeferConceptIndexingInterceptor;
@ -73,12 +69,6 @@ import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor;
}, indexes= {
@Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList="INDEX_STATUS")
})
@AnalyzerDefs({
@AnalyzerDef(name = "conceptParentPidsAnalyzer",
tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
filters = {
})
})
public class TermConcept implements Serializable {
private static final int MAX_DESC_LENGTH = 400;
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.
* #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.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.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
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.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.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 com.google.common.collect.Lists;
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;
import javax.persistence.EntityManager;
import java.util.*;
import java.util.concurrent.*;
public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
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> {
private final IDao myCallingDao;
@ -434,6 +459,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
private int myCountSaved = 0;
private String mySearchUuid;
/**
* Constructor
*/
public SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, String theSearchUuid) {
mySearch = theSearch;
myCallingDao = theCallingDao;
@ -443,6 +471,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
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() {
ourLog.trace("Awaiting initial sync");
do {
@ -451,6 +484,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
break;
}
} catch (InterruptedException e) {
// Shouldn't happen
throw new InternalErrorException(e);
}
} while (mySearch.getStatus() == SearchStatusEnum.LOADING);
@ -459,11 +493,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
return mySearch.getTotalCount();
}
/**
* This is the method which actually performs the search.
* It is called automatically by the thread pool.
*/
@Override
public Void call() throws Exception {
StopWatch sw = new StopWatch();
try {
// Create an initial search in the DB and give it an ID
saveSearch();
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
@ -480,7 +519,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
} 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;
if (t instanceof BaseServerResponseException) {
@ -535,13 +576,27 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
Class<? extends IBaseResource> resourceTypeClass = myContext.getResourceDefinition(myResourceType).getImplementingClass();
ISearchBuilder sb = myCallingDao.newSearchBuilder();
sb.setType(resourceTypeClass, myResourceType);
Iterator<Long> theResultIter = sb.createQuery(myParams, mySearchUuid);
Iterator<Long> theResultIterator = sb.createQuery(myParams, mySearchUuid);
while (theResultIter.hasNext()) {
myUnsyncedPids.add(theResultIter.next());
if (myUnsyncedPids.size() >= mySyncSize) {
saveUnsynced(theResultIter);
while (theResultIterator.hasNext()) {
myUnsyncedPids.add(theResultIterator.next());
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) {
try {
Thread.sleep(myLoadingThrottleForUnitTests);
@ -549,9 +604,12 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
// ignore
}
}
// Check if an abort got requested
Validate.isTrue(myAbortRequested == false, "Abort has been requested");
}
saveUnsynced(theResultIter);
saveUnsynced(theResultIterator);
}
public CountDownLatch getCompletionLatch() {
@ -648,22 +706,25 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
mySearch.setStatus(SearchStatusEnum.FINISHED);
}
}
mySearch.setNumFound(myCountSaved);
int numSynced;
synchronized (mySyncedPids) {
numSynced = mySyncedPids.size();
}
if (myDaoConfig.getCountSearchResultsUpTo() == null ||
myDaoConfig.getCountSearchResultsUpTo() <= 0 ||
myDaoConfig.getCountSearchResultsUpTo() <= numSynced) {
myInitialCollectionLatch.countDown();
}
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;
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.validation.ResultSeverityEnum;
import org.apache.commons.dbcp2.BasicDataSource;
@ -60,6 +60,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
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.lucene_version","LUCENE_CURRENT");
return extraProperties;

View File

@ -1,5 +1,15 @@
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.subscription.email.IEmailSender;
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.hbm2ddl.auto", "update");
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.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.autoregister_listeners", "true");

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.config;
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.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
@ -125,6 +126,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
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.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.autoregister_listeners", "true");

View File

@ -56,6 +56,30 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
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) {
try {
assertNotGone(theId);

View File

@ -55,6 +55,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
public final void after() {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical());
myDaoConfig.setEnforceReferentialIntegrityOnDelete(new DaoConfig().isEnforceReferentialIntegrityOnDelete());
}
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
public void beforeDisableResultReuse() {

View File

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

View File

@ -3149,17 +3149,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
.count(1)
.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());
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), 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
assertEquals(null, found.getTotalElement().getValue());
assertEquals(1, found.getEntry().size());
assertEquals(msg,null, found.getTotalElement().getValue());
assertEquals(msg, 1, found.getEntry().size());
assertThat(msg, sw.getMillis(), lessThan(1000L));
}
@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.toLowerCase()),Matchers.<String>empty());
assertThat(sw.getMillis(), lessThan(1000L));
// If this fails under load, try increasing the throttle above
assertEquals(null, found.getTotalElement().getValue());
assertEquals(1, found.getEntry().size());
assertThat(sw.getMillis(), lessThan(1000L));
}
@Test

View File

@ -5,6 +5,7 @@ import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
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_structured_entries", "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.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -1,10 +1,9 @@
package ca.uhn.fhir.jpa.demo;
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.search.LuceneSearchMappingFactory;
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.LoggingInterceptor;
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_structured_entries", "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.indexBase", "target/lucenefiles");
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_structured_entries", "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.indexBase", "target/lucenefiles");
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.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
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_structured_entries", "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.indexBase", myFhirLuceneLocation);
extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT");

View File

@ -5,6 +5,7 @@ import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
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_structured_entries", "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.indexBase", myFhirLuceneLocation);
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.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
@ -65,6 +66,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
retVal.setAllowExternalReferences(true);
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu2");
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu2");
retVal.setCountSearchResultsUpTo(TestR4Config.COUNT_SEARCH_RESULTS_UP_TO);
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_structured_entries", "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.indexBase", myFhirLuceneLocation);
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -5,6 +5,7 @@ import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.DerbyTenSevenDialect;
@ -54,6 +55,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
retVal.setAllowExternalReferences(true);
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu3");
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu3");
retVal.setCountSearchResultsUpTo(TestR4Config.COUNT_SEARCH_RESULTS_UP_TO);
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_structured_entries", "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.indexBase", myFhirLuceneLocation);
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -5,11 +5,11 @@ import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.DerbyTenSevenDialect;
import org.hibernate.dialect.PostgreSQL94Dialect;
import org.hibernate.dialect.PostgreSQL95Dialect;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire;
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_structured_entries", "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.indexBase", myFhirLuceneLocation);
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");

View File

@ -86,10 +86,41 @@ public class RestfulServerUtils {
}
}
if (elements != null && elements.size() > 0) {
Set<String> newElements = new HashSet<String>();
Set<String> newElements = new HashSet<>();
for (String next : elements) {
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.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo);
}
@ -147,6 +178,19 @@ public class RestfulServerUtils {
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();
} catch (UnsupportedEncodingException e) {
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;");
IBaseReference securityContext = BinaryUtil.getSecurityContext(theServer.getFhirContext(), bin);
String securityContextRef = securityContext.getReferenceElement().getValue();
if (isNotBlank(securityContextRef)) {
response.addHeader(Constants.HEADER_X_SECURITY_CONTEXT, securityContextRef);
if (securityContext != null) {
String securityContextRef = securityContext.getReferenceElement().getValue();
if (isNotBlank(securityContextRef)) {
response.addHeader(Constants.HEADER_X_SECURITY_CONTEXT, securityContextRef);
}
}
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.addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes);
bundleFactory.addResourcesToBundle(new ArrayList<>(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes);
if (theServer.getPagingProvider() != null) {
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 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("Actual : {}", val);
assertEquals(expected, val);

View File

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

View File

@ -1,13 +1,8 @@
package ca.uhn.fhir.context;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import ca.uhn.fhir.rest.client.MyPatientWithExtensions;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Patient;
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.Test;
import ca.uhn.fhir.rest.client.MyPatientWithExtensions;
import ca.uhn.fhir.util.TestUtil;
import java.util.ArrayList;
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 {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContextDstu3Test.class);
private static FhirContext ourCtx = FhirContext.forDstu3();
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@SuppressWarnings("deprecation")
@Test
public void testAutoDetectVersion() {
@ -35,6 +32,17 @@ public class FhirContextDstu3Test {
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
*/
@ -44,7 +52,7 @@ public class FhirContextDstu3Test {
assertEquals(Reference.class, ourCtx.getElementDefinition("Reference").getImplementingClass());
assertEquals(Reference.class, ourCtx.getElementDefinition("REFerence").getImplementingClass());
}
/**
* See #344
*/
@ -57,73 +65,92 @@ public class FhirContextDstu3Test {
}
@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());
public void testInitialisationThreadSafety() {
final FhirContext ctx = FhirContext.forDstu3();
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());
}
/**
* 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
public void testQueryBoundCode() {
RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class);
String childName = "gender";
BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName);
ourLog.trace(genderChild.getClass().getName());
assertEquals(AdministrativeGender.class, genderChild.getBoundEnumType());
}
@Test
public void testQueryNonBoundCode() {
RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class);
String childName = "name";
BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName);
ourLog.trace(genderChild.getClass().getName());
assertEquals(null, genderChild.getBoundEnumType());
}
@Test
public void testInitialisationThreadSafety() {
final FhirContext ctx = FhirContext.forDstu3();
@AfterClass
public static void afterClassClearContext() {
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;
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.rest.annotation.RequiredParam;
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.param.TokenAndListParam;
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 {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchR4Test.class);
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static TokenAndListParam ourIdentifiers;
private static String ourLastMethod;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchR4Test.class);
private static int ourPort;
private static Server ourServer;
@ -57,74 +61,63 @@ public class SearchR4Test {
ourIdentifiers = null;
}
@Test
public void testSearchNormal() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException {
CloseableHttpResponse status = ourClient.execute(httpGet);
Bundle bundle;
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("search", ourLastMethod);
assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
assertEquals(theExpectEncoding, ct);
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
assertEquals(10, bundle.getEntry().size());
String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertNotNull(linkNext);
} finally {
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
public void testPagingPreservesEncodingJson() throws Exception {
public void testPagingPreservesElements() throws Exception {
HttpGet httpGet;
String linkNext;
Bundle bundle;
String linkSelf;
// 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);
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();
assertThat(linkNext, containsString("_format=json"));
assertThat(linkNext, containsString("_elements=name"));
ourLog.info(toJson(bundle));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
assertThat(linkNext, containsString("_elements=name"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
assertThat(linkNext, containsString("_elements=name"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
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
public void testPagingPreservesEncodingXml() throws Exception {
public void testPagingPreservesEncodingJson() throws Exception {
HttpGet httpGet;
String linkNext;
Bundle bundle;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), containsString("active"));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
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 {
CloseableHttpResponse status = ourClient.execute(httpGet);
@Test
public void testPagingPreservesEncodingXml() throws Exception {
HttpGet httpGet;
String linkNext;
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 {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
assertEquals(theExpectEncoding, ct);
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
assertEquals(10, bundle.getEntry().size());
String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertNotNull(linkNext);
assertEquals("search", ourLastMethod);
assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
} finally {
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
public void testSearchWithPostAndInvalidParameters() throws Exception {
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
@ -293,14 +337,14 @@ public class SearchR4Test {
client.registerInterceptor(interceptor);
try {
client
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.prettyPrint()
.usingStyle(SearchStyleEnum.POST)
.returnBundle(org.hl7.fhir.r4.model.Bundle.class)
.encodedJson()
.execute();
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.prettyPrint()
.usingStyle(SearchStyleEnum.POST)
.returnBundle(org.hl7.fhir.r4.model.Bundle.class)
.encodedJson()
.execute();
fail();
} 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]]"));
@ -308,6 +352,10 @@ public class SearchR4Test {
}
private String toJson(Bundle theBundle) {
return ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theBundle);
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
@ -349,16 +397,17 @@ public class SearchR4Test {
@SuppressWarnings("rawtypes")
@Search()
public List search(
@RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
@RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
ourLastMethod = "search";
ourIdentifiers = theIdentifiers;
ArrayList<Patient> retVal = new ArrayList<Patient>();
for (int i = 0; i < 200; i++) {
Patient patient = new Patient();
patient.addName(new HumanName().setFamily("FAMILY"));
patient.setActive(true);
patient.getIdElement().setValue("Patient/" + i);
retVal.add((Patient) patient);
retVal.add(patient);
}
return retVal;
}

23
pom.xml
View File

@ -370,6 +370,14 @@
<id>malcolmm83</id>
<name>Malcolm McRoberts</name>
</developer>
<developer>
<id>mouellet</id>
<name>Mathieu Ouellet</name>
</developer>
<developer>
<id>JiajingLiang</id>
<name>Jiajing Liang</name>
</developer>
</developers>
<licenses>
@ -403,6 +411,7 @@
<phloc_schematron_version>2.7.1</phloc_schematron_version>
<phloc_commons_version>4.4.11</phloc_commons_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>
<!-- 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>
<version>${spring_version}</version>
</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>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
@ -1032,7 +1048,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.2</version>
<version>3.7.0</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
@ -1045,6 +1061,10 @@
<testTarget>1.8</testTarget>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<encoding>UTF-8</encoding>
<fork>true</fork>
<meminitial>128m</meminitial>
<maxmem>1600m</maxmem>
</configuration>
<dependencies>
<dependency>
@ -1880,6 +1900,7 @@
<module>example-projects/hapi-fhir-standalone-overlay-example</module>
<module>hapi-fhir-jacoco</module>
<module>hapi-fhir-igpacks</module>
<module>hapi-fhir-spring-boot</module>
<!--<module>hapi-fhir-osgi-core</module>-->
</modules>
</profile>

View File

@ -154,7 +154,7 @@
JAX-RS client framework now supports the ability to
register your own JAX-RS Component Classes against the client,
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 type="fix" issue="717">
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
request with fix and test case!
</action>
<action type="add">
Bundle resources did not have their version encoded when serializing
in FHIR resource (XML/JSON) format.
</action>
<action type="add">
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
the raw binary with the server, as opposed to exchanging a FHIR resource).
</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 version="3.0.0" date="2017-09-27">
<action type="add">
@ -545,7 +577,7 @@ Bundle bundle = client.search().forResource(Patient.class)
<action type="fix" issue="686">
Correct an issue in the validator (DSTU3/R4) where elements were not always
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 type="add" issue="701">
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 type="fix" issue="695">
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 type="fix" issue="710">
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">
DaoConfig#setAllowInlineMatchUrlReferences() now defaults to
<![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!
</action>
<action type="add" issue="609">
@ -1860,7 +1892,7 @@ Bundle bundle = client.search().forResource(Patient.class)
</action>
<action type="fix" issue="426">
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 type="fix">
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!
</action>
<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:
<![CDATA[
<ul>