diff --git a/.gitignore b/.gitignore index 09734b19d32..db79bca9c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -141,6 +141,8 @@ local.properties **/.target **/.project **/.classpath +**/.factorypath +**/.springBeans # PDT-specific diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000000..037f9f0e7a7 --- /dev/null +++ b/appveyor.yml @@ -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 diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 8b0168ae690..93b7bdaed96 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -116,10 +116,10 @@ spring-jcl - - changelog.txt + + changelog.txt javac.bat - + diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java index 70751c6f170..e7f00c4d037 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java @@ -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"); diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java new file mode 100644 index 00000000000..840a47f4db4 --- /dev/null +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java @@ -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> additionalHttpHeaders = new HashMap<>(); + + public AdditionalRequestHeadersInterceptor() { + this(new HashMap>()); + } + + public AdditionalRequestHeadersInterceptor(Map> 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 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 getHeaderValues(String headerName) { + if (additionalHttpHeaders.get(headerName) == null) { + additionalHttpHeaders.put(headerName, new ArrayList()); + } + 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> 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. + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java index 4df1d50cfd1..056710bcca0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java @@ -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; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java index 800e71ee10f..0cb567950db 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java @@ -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); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java new file mode 100644 index 00000000000..1f950f28ed4 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java @@ -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; + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 49950d34dd3..6f2a727e59e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -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 { 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 resourceTypeClass = myContext.getResourceDefinition(myResourceType).getImplementingClass(); ISearchBuilder sb = myCallingDao.newSearchBuilder(); sb.setType(resourceTypeClass, myResourceType); - Iterator theResultIter = sb.createQuery(myParams, mySearchUuid); + Iterator 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(); - } } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java index d2852edca9b..49c9107ce28 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java @@ -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; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index baa0a217359..46fb60ba025 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -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"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index 82b78e27799..d40ea5d2284 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -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"); diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java index 90f791c0919..69569f72085 100644 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java +++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java @@ -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"); diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java index 0d45ad6b25d..9a6b52e0a2d 100644 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java +++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java @@ -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"); diff --git a/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java b/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java index 4a19ef39012..ae55d7a1ed3 100644 --- a/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java +++ b/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java @@ -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"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java index bf568c7d1c9..7b94f1b82df 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java @@ -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"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java index 45be4c9e38b..2d82c131f43 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java @@ -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"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java index 7b6dabda413..095ca6c39fd 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java @@ -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"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java index 11189d2f7bc..1f5dcb028d0 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java @@ -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"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java index d76e45b40f2..6a96849ea0a 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java @@ -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"); diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml new file mode 100644 index 00000000000..b66479550f3 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -0,0 +1,118 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-autoconfigure + + jar + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + ca.uhn.hapi.fhir + hapi-fhir-base + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-server + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-base + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-client + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-client-okhttp + ${project.version} + true + + + javax.servlet + javax.servlet-api + true + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework + spring-web + test + + + com.h2database + h2 + test + + + ch.qos.logback + logback-classic + test + + + org.slf4j + log4j-over-slf4j + test + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu2 + ${project.version} + test + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu2.1 + ${project.version} + test + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu3 + ${project.version} + test + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java new file mode 100644 index 00000000000..3a108336b07 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java @@ -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 resourceProviders; + + private final IPagingProvider pagingProvider; + + private final List interceptors; + + private final List customizers; + + public FhirRestfulServerConfiguration( + FhirProperties properties, + FhirContext fhirContext, + ObjectProvider> resourceProviders, + ObjectProvider pagingProvider, + ObjectProvider> interceptors, + ObjectProvider> 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 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 clientInterceptors; + + public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider> 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; + } + } + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java new file mode 100644 index 00000000000..5bb8d3cc2f5 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java @@ -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; + } + } +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java new file mode 100644 index 00000000000..3f19d1b58a4 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java @@ -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); +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..c761b7cf3e0 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=ca.uhn.fhir.spring.boot.autoconfigure.FhirAutoConfiguration \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java new file mode 100644 index 00000000000..3b3023e4eff --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java @@ -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); + } + + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/application.yml new file mode 100644 index 00000000000..f3a8cf9c934 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/application.yml @@ -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 diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/logback-test.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..ee274734003 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/logback-test.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml new file mode 100644 index 00000000000..963adda0fcb --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-sample-client-apache + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu3 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-client + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java new file mode 100644 index 00000000000..e553eebc646 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java @@ -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(); + } + }; + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/resources/application.yml new file mode 100644 index 00000000000..33eb38f1d70 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/resources/application.yml @@ -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 \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml new file mode 100644 index 00000000000..69cc6e9f6b1 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-sample-client-okhttp + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu3 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-client-okhttp + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java new file mode 100644 index 00000000000..6286d41b824 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java @@ -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(); + } + }; + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/resources/application.yml new file mode 100644 index 00000000000..33eb38f1d70 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/resources/application.yml @@ -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 \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml new file mode 100644 index 00000000000..439a5ce9252 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -0,0 +1,67 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-sample-server-jersey + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-jersey + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu3 + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java new file mode 100644 index 00000000000..76b50ec7101 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java @@ -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(); + } +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java new file mode 100644 index 00000000000..9f12b953a26 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java @@ -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 { + + private static Long counter = 1L; + + private static final ConcurrentHashMap 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 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; + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/resources/application.yml new file mode 100644 index 00000000000..325409646b7 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/resources/application.yml @@ -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 \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/test/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplicationTest.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/test/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplicationTest.java new file mode 100644 index 00000000000..59f41dea74b --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/test/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplicationTest.java @@ -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 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 entity = this.restTemplate.getForEntity( + "/fhir/Patient/1", + String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(entity.getBody()).contains("\"family\": \"Van Houte\""); + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml new file mode 100644 index 00000000000..a7ecaf84aa2 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml @@ -0,0 +1,75 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-sample-server-jpa + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-jersey + + + org.springframework.boot + spring-boot-starter-data-jpa + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-base + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + ${project.version} + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplication.java new file mode 100644 index 00000000000..721e4b8d3a1 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplication.java @@ -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); + } +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml new file mode 100644 index 00000000000..2c2f591eb69 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml @@ -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 diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/test/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplicationTest.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/test/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplicationTest.java new file mode 100644 index 00000000000..bc36ba6eb25 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/test/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplicationTest.java @@ -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); + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml new file mode 100644 index 00000000000..29511655859 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-samples + pom + + + hapi-fhir-spring-boot-sample-client-apache + hapi-fhir-spring-boot-sample-client-okhttp + hapi-fhir-spring-boot-sample-server-jersey + hapi-fhir-spring-boot-sample-server-jpa + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml new file mode 100644 index 00000000000..094a37377e3 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-starter + + jar + + + + org.springframework.boot + spring-boot-starter + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-autoconfigure + ${project.version} + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml new file mode 100644 index 00000000000..a266e1653ed --- /dev/null +++ b/hapi-fhir-spring-boot/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir + 3.1.0-SNAPSHOT + ../pom.xml + + + hapi-fhir-spring-boot + pom + + + hapi-fhir-spring-boot-autoconfigure + hapi-fhir-spring-boot-starter + hapi-fhir-spring-boot-samples + + + \ No newline at end of file diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index f303ccd1785..d796ef60380 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -70,13 +70,6 @@ true - - org.apache.maven.plugins - maven-compiler-plugin - - true - - diff --git a/pom.xml b/pom.xml index 6c50a8277a6..aa9b1c4c5e3 100644 --- a/pom.xml +++ b/pom.xml @@ -370,6 +370,14 @@ malcolmm83 Malcolm McRoberts + + mouellet + Mathieu Ouellet + + + JiajingLiang + Jiajing Liang + @@ -403,6 +411,7 @@ 2.7.1 4.4.11 5.0.0.RELEASE + 1.5.6.RELEASE 3.0.7.RELEASE @@ -970,6 +979,13 @@ spring-websocket ${spring_version} + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + org.thymeleaf thymeleaf @@ -1032,7 +1048,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.2 + 3.7.0 1.7 1.7 @@ -1045,6 +1061,10 @@ 1.8 true UTF-8 + + true + 128m + 1600m @@ -1880,6 +1900,7 @@ example-projects/hapi-fhir-standalone-overlay-example hapi-fhir-jacoco hapi-fhir-igpacks + hapi-fhir-spring-boot diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 65d74758394..21cee568c1f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -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! Processing of the If-Modified-Since header on FHIR read operations was reversed, @@ -195,6 +195,23 @@ explicitly listed, e.g. _include=Bundle.total]]>. Thanks to @parisni for reporting! + + 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! + + + 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! + + + 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! + @@ -560,7 +577,7 @@ Bundle bundle = client.search().forResource(Patient.class) 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! Testing UI now has a dropdown for modifiers on token search. Thanks @@ -574,7 +591,7 @@ Bundle bundle = client.search().forResource(Patient.class) 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! Fix a bug in REST Hook Subscription interceptors which prevented subscriptions @@ -895,7 +912,7 @@ Bundle bundle = client.search().forResource(Patient.class) DaoConfig#setAllowInlineMatchUrlReferences() now defaults to true]]> 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! @@ -1875,7 +1892,7 @@ Bundle bundle = client.search().forResource(Patient.class) 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! When committing a transaction in JPA server @@ -2137,7 +2154,7 @@ Bundle bundle = client.search().forResource(Patient.class) array instead of a string. Thanks to David Hay for reporting! - 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: