Merge branch 'master' into hapi3_refactor

This commit is contained in:
James 2017-06-28 06:21:22 -04:00
commit fbe2f98a02
13 changed files with 579 additions and 252 deletions

View File

@ -334,6 +334,16 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
continue; continue;
} }
nextId = nextValue.getReferenceElement(); nextId = nextValue.getReferenceElement();
/*
* This can only really happen if the DAO is being called
* programatically with a Bundle (not through the FHIR REST API)
* but Smile does this
*/
if (nextId.isEmpty() && nextValue.getResource() != null) {
nextId = nextValue.getResource().getIdElement();
}
if (nextId.isEmpty() || nextId.getValue().startsWith("#")) { if (nextId.isEmpty() || nextId.getValue().startsWith("#")) {
// This is a blank or contained resource reference // This is a blank or contained resource reference
continue; continue;
@ -2046,7 +2056,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return paramMap; return paramMap;
} }
protected static List<NameValuePair> translateMatchUrl(String theMatchUrl) { public static List<NameValuePair> translateMatchUrl(String theMatchUrl) {
List<NameValuePair> parameters; List<NameValuePair> parameters;
String matchUrl = theMatchUrl; String matchUrl = theMatchUrl;
int questionMarkIndex = matchUrl.indexOf('?'); int questionMarkIndex = matchUrl.indexOf('?');

View File

@ -273,19 +273,24 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Notify interceptors // Notify interceptors
IdDt idToDelete = entity.getIdDt(); IdDt idToDelete = entity.getIdDt();
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, idToDelete.getResourceType(), idToDelete); if (theRequestDetails != null) {
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails); ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, idToDelete.getResourceType(), idToDelete);
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
}
// Perform delete // Perform delete
Date updateTime = new Date(); Date updateTime = new Date();
updateEntity(null, entity, updateTime, updateTime); updateEntity(null, entity, updateTime, updateTime);
// Notify JPA interceptors // Notify JPA interceptors
T resourceToDelete = toResource(myResourceType, entity, false); T resourceToDelete = toResource(myResourceType, entity, false);
theRequestDetails.getRequestOperationCallback().resourceDeleted(resourceToDelete); if (theRequestDetails != null) {
for (IServerInterceptor next : getConfig().getInterceptors()) { theRequestDetails.getRequestOperationCallback().resourceDeleted(resourceToDelete);
if (next instanceof IJpaServerInterceptor) { ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, idToDelete.getResourceType(), idToDelete);
((IJpaServerInterceptor) next).resourceDeleted(requestDetails, entity); for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IJpaServerInterceptor) {
((IJpaServerInterceptor) next).resourceDeleted(requestDetails, entity);
}
} }
} }
for (IServerInterceptor next : getConfig().getInterceptors()) { for (IServerInterceptor next : getConfig().getInterceptors()) {

View File

@ -247,7 +247,6 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get(); QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get();
//@formatter:off
Query textQuery = qb Query textQuery = qb
.phrase() .phrase()
.withSlop(2) .withSlop(2)
@ -261,7 +260,6 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
.must(qb.keyword().onField("myResourceLinks.myTargetResourcePid").matching(pid).createQuery()) .must(qb.keyword().onField("myResourceLinks.myTargetResourcePid").matching(pid).createQuery())
.must(textQuery) .must(textQuery)
.createQuery(); .createQuery();
//@formatter:on
FullTextQuery ftq = em.createFullTextQuery(query, ResourceTable.class); FullTextQuery ftq = em.createFullTextQuery(query, ResourceTable.class);
ftq.setProjection("myContentText"); ftq.setProjection("myContentText");
@ -289,14 +287,6 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
formatter.setAnalyzer("myContentTextEdgeNGram"); formatter.setAnalyzer("myContentTextEdgeNGram");
highlighter.getBestFragments(analyzer.tokenStream("myContentTextEdgeNGram", nextValue), nextValue, 10); highlighter.getBestFragments(analyzer.tokenStream("myContentTextEdgeNGram", nextValue), nextValue, 10);
// formatter.setAnalyzer("myContentText");
// highlighter.getBestFragments(analyzer.tokenStream("myContentText", nextValue), nextValue, 10);
// formatter.setAnalyzer("myContentTextNGram");
// highlighter.getBestFragments(analyzer.tokenStream("myContentTextNGram", nextValue), nextValue, 10);
// formatter.setAnalyzer("myContentTextEdgeNGram");
// highlighter.getBestFragments(analyzer.tokenStream("myContentTextEdgeNGram", nextValue), nextValue, 10);
// formatter.setAnalyzer("myContentTextPhonetic");
// highlighter.getBestFragments(analyzer.tokenStream("myContentTextPhonetic", nextValue), nextValue, 10);
} catch (Exception e) { } catch (Exception e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }

View File

@ -29,9 +29,12 @@ import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -63,6 +66,7 @@ import ca.uhn.fhir.rest.server.interceptor.*;
public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterceptorAdapter { public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterceptorAdapter {
private static final Integer MAX_SUBSCRIPTION_RESULTS = 10000;
private static volatile ExecutorService executor; private static volatile ExecutorService executor;
private final static int MAX_THREADS = 1; private final static int MAX_THREADS = 1;
@ -71,13 +75,13 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
@Autowired @Autowired
private FhirContext myFhirContext; private FhirContext myFhirContext;
private boolean myNotifyOnDelete = false;
private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>();
@Autowired @Autowired
@Qualifier("mySubscriptionDaoDstu2") @Qualifier("mySubscriptionDaoDstu2")
private IFhirResourceDao<Subscription> mySubscriptionDao; private IFhirResourceDao<Subscription> mySubscriptionDao;
private boolean myNotifyOnDelete = false;
private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>();
/** /**
* Check subscriptions and send notifications or payload * Check subscriptions and send notifications or payload
* *
@ -205,6 +209,7 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
req.setSubRequest(true); req.setSubRequest(true);
IFhirResourceDao<? extends IBaseResource> responseDao = mySubscriptionDao.getDao(responseResourceDef.getImplementingClass()); IFhirResourceDao<? extends IBaseResource> responseDao = mySubscriptionDao.getDao(responseResourceDef.getImplementingClass());
responseCriteriaUrl.setCount(MAX_SUBSCRIPTION_RESULTS);
IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req); IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
return responseResults; return responseResults;
} }
@ -265,7 +270,12 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
RequestDetails req = new ServletSubRequestDetails(); RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true); req.setSubRequest(true);
map.setCount(MAX_SUBSCRIPTION_RESULTS);
IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req); IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req);
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
ourLog.error("Currently over "+MAX_SUBSCRIPTION_RESULTS+" subscriptions. Some subscriptions have not been loaded.");
}
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size()); List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
for (IBaseResource resource : resourceList) { for (IBaseResource resource : resourceList) {
@ -398,4 +408,58 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
public void setSubscriptionDao(IFhirResourceDao<Subscription> theSubscriptionDao) { public void setSubscriptionDao(IFhirResourceDao<Subscription> theSubscriptionDao) {
mySubscriptionDao = theSubscriptionDao; mySubscriptionDao = theSubscriptionDao;
} }
@Override
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theDetails) {
//check the subscription criteria to see if its valid before creating or updating a subscription
if (RestOperationTypeEnum.CREATE.equals(theOperation) || RestOperationTypeEnum.UPDATE.equals(theOperation)) {
String resourceType = theDetails.getResourceType();
ourLog.info("prehandled resource type: " + resourceType);
if (resourceType != null && resourceType.equals(Subscription.class.getSimpleName())) {
Subscription subscription = (Subscription) theDetails.getResource();
if (subscription != null) {
checkSubscriptionCriterias(subscription);
}
}
}
super.incomingRequestPreHandled(theOperation, theDetails);
}
private void checkSubscriptionCriterias(Subscription subscription){
try {
IBundleProvider results = executeSubscriptionCriteria(subscription, null);
}catch (Exception e){
throw new InvalidRequestException("Invalid criteria");
}
}
private IBundleProvider executeSubscriptionCriteria(Subscription subscription, IIdType idType){
//run the subscriptions query and look for matches, add the id as part of the criteria to avoid getting matches of previous resources rather than the recent resource
String criteria = subscription.getCriteria();
if(idType != null) {
criteria += "&_id=" + idType.getResourceType() + "/" + idType.getIdPart();
}
IBundleProvider results = getBundleProvider(criteria);
return results;
}
/**
* Get the encoding from the criteria or return JSON encoding if its not found
*
* @param criteria
* @return
*/
private EncodingEnum getEncoding(String criteria) {
//check criteria
String params = criteria.substring(criteria.indexOf('?') + 1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
return EncodingEnum.forContentType(nameValuePair.getValue());
}
}
return EncodingEnum.JSON;
}
} }

View File

@ -30,9 +30,12 @@ import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.dstu3.model.Subscription;
@ -61,6 +64,7 @@ import ca.uhn.fhir.rest.server.interceptor.*;
public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterceptorAdapter { public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterceptorAdapter {
private static final Integer MAX_SUBSCRIPTION_RESULTS = 10000;
private static volatile ExecutorService executor; private static volatile ExecutorService executor;
private final static int MAX_THREADS = 1; private final static int MAX_THREADS = 1;
@ -68,21 +72,12 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
@Autowired @Autowired
private FhirContext myFhirContext; private FhirContext myFhirContext;
public void setFhirContext(FhirContext theFhirContext) {
myFhirContext = theFhirContext;
}
public void setSubscriptionDao(IFhirResourceDao<Subscription> theSubscriptionDao) {
mySubscriptionDao = theSubscriptionDao;
}
@Autowired @Autowired
@Qualifier("mySubscriptionDaoDstu3") @Qualifier("mySubscriptionDaoDstu3")
private IFhirResourceDao<Subscription> mySubscriptionDao; private IFhirResourceDao<Subscription> mySubscriptionDao;
private boolean notifyOnDelete = false; private boolean notifyOnDelete = false;
private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>(); private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>();
/** /**
@ -205,6 +200,7 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
req.setSubRequest(true); req.setSubRequest(true);
IFhirResourceDao<? extends IBaseResource> responseDao = mySubscriptionDao.getDao(responseResourceDef.getImplementingClass()); IFhirResourceDao<? extends IBaseResource> responseDao = mySubscriptionDao.getDao(responseResourceDef.getImplementingClass());
responseCriteriaUrl.setCount(MAX_SUBSCRIPTION_RESULTS);
IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req); IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
return responseResults; return responseResults;
} }
@ -254,7 +250,6 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
return entity; return entity;
} }
/** /**
* Read the existing subscriptions from the database * Read the existing subscriptions from the database
*/ */
@ -266,7 +261,12 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
RequestDetails req = new ServletSubRequestDetails(); RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true); req.setSubRequest(true);
map.setCount(MAX_SUBSCRIPTION_RESULTS);
IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req); IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req);
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
ourLog.error("Currently over "+MAX_SUBSCRIPTION_RESULTS+" subscriptions. Some subscriptions have not been loaded.");
}
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size()); List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
for (IBaseResource resource : resourceList) { for (IBaseResource resource : resourceList) {
@ -388,7 +388,69 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
} }
} }
public void setFhirContext(FhirContext theFhirContext) {
myFhirContext = theFhirContext;
}
public void setNotifyOnDelete(boolean notifyOnDelete) { public void setNotifyOnDelete(boolean notifyOnDelete) {
this.notifyOnDelete = notifyOnDelete; this.notifyOnDelete = notifyOnDelete;
} }
public void setSubscriptionDao(IFhirResourceDao<Subscription> theSubscriptionDao) {
mySubscriptionDao = theSubscriptionDao;
}
@Override
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theDetails) {
//check the subscription criteria to see if its valid before creating or updating a subscription
if (RestOperationTypeEnum.CREATE.equals(theOperation) || RestOperationTypeEnum.UPDATE.equals(theOperation)) {
String resourceType = theDetails.getResourceType();
ourLog.info("prehandled resource type: " + resourceType);
if (resourceType != null && resourceType.equals(Subscription.class.getSimpleName())) {
Subscription subscription = (Subscription) theDetails.getResource();
if (subscription != null) {
checkSubscriptionCriterias(subscription);
}
}
}
super.incomingRequestPreHandled(theOperation, theDetails);
}
private void checkSubscriptionCriterias(Subscription subscription){
try {
IBundleProvider results = executeSubscriptionCriteria(subscription, null);
}catch (Exception e){
throw new InvalidRequestException("Invalid criteria");
}
}
private IBundleProvider executeSubscriptionCriteria(Subscription subscription, IIdType idType){
//run the subscriptions query and look for matches, add the id as part of the criteria to avoid getting matches of previous resources rather than the recent resource
String criteria = subscription.getCriteria();
if(idType != null) {
criteria += "&_id=" + idType.getResourceType() + "/" + idType.getIdPart();
}
IBundleProvider results = getBundleProvider(criteria);
return results;
}
/**
* Get the encoding from the criteria or return JSON encoding if its not found
*
* @param criteria
* @return
*/
private EncodingEnum getEncoding(String criteria) {
//check criteria
String params = criteria.substring(criteria.indexOf('?') + 1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
return EncodingEnum.forContentType(nameValuePair.getValue());
}
}
return EncodingEnum.JSON;
}
} }

View File

@ -542,6 +542,60 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
} }
} }
@Test
public void testTransactionWithReferenceUuid() {
Bundle request = new Bundle();
Patient p = new Patient();
p.setActive(true);
p.setId(IdType.newRandomUuid());
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId());
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setReference(p.getId());
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
Bundle resp = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue();
assertThat(patientId, startsWith("Patient/"));
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("subject", new ReferenceParam(patientId));
IBundleProvider found = myObservationDao.search(params);
assertEquals(1, found.size().intValue());
}
@Test
public void testTransactionWithReferenceResource() {
Bundle request = new Bundle();
Patient p = new Patient();
p.setActive(true);
p.setId(IdType.newRandomUuid());
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId());
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setResource(p);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
Bundle resp = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue();
assertThat(patientId, startsWith("Patient/"));
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("subject", new ReferenceParam(patientId));
IBundleProvider found = myObservationDao.search(params);
assertEquals(1, found.size().intValue());
}
@Test @Test
public void testTransactionCreateInlineMatchUrlWithOneMatch() { public void testTransactionCreateInlineMatchUrlWithOneMatch() {
String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch"; String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch";

View File

@ -0,0 +1,125 @@
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.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;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;
/**
* This is the primary configuration file for the example server
*/
@Configuration
@EnableTransactionManagement()
public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 {
/**
* Configure FHIR properties around the the JPA server via this bean
*/
@Bean()
public DaoConfig daoConfig() {
DaoConfig retVal = new DaoConfig();
retVal.setSubscriptionEnabled(true);
retVal.setSubscriptionPollDelay(5000);
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
retVal.setAllowMultipleDelete(true);
return retVal;
}
/**
* The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a
* directory called "jpaserver_derby_files".
*
* A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource.
*/
@Bean(destroyMethod = "close")
public DataSource dataSource() {
BasicDataSource retVal = new BasicDataSource();
retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver());
retVal.setUrl("jdbc:derby:directory:target/jpaserver_derby_files;create=true");
retVal.setUsername("");
retVal.setPassword("");
return retVal;
}
@Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean();
retVal.setPersistenceUnitName("HAPI_PU");
retVal.setDataSource(dataSource());
retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity");
retVal.setPersistenceProvider(new HibernatePersistenceProvider());
retVal.setJpaProperties(jpaProperties());
return retVal;
}
private Properties jpaProperties() {
Properties extraProperties = new Properties();
extraProperties.put("hibernate.dialect", org.hibernate.dialect.DerbyTenSevenDialect.class.getName());
extraProperties.put("hibernate.format_sql", "true");
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.jdbc.batch_size", "20");
extraProperties.put("hibernate.cache.use_query_cache", "false");
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.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
// extraProperties.put("hibernate.search.default.worker.execution", "async");
return extraProperties;
}
/**
* Do some fancy logging to create a nice access log that has details about each incoming request.
*/
public IServerInterceptor loggingInterceptor() {
LoggingInterceptor retVal = new LoggingInterceptor();
retVal.setLoggerName("fhirtest.access");
retVal.setMessageFormat(
"Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]");
retVal.setLogExceptions(true);
retVal.setErrorMessageFormat("ERROR - ${requestVerb} ${requestUrl}");
return retVal;
}
/**
* This interceptor adds some pretty syntax highlighting in responses when a browser is detected
*/
@Bean(autowire = Autowire.BY_TYPE)
public IServerInterceptor responseHighlighterInterceptor() {
ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor();
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public IServerInterceptor subscriptionSecurityInterceptor() {
SubscriptionsRequireManualActivationInterceptorDstu2 retVal = new SubscriptionsRequireManualActivationInterceptorDstu2();
return retVal;
}
@Bean()
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory);
return retVal;
}
}

View File

@ -0,0 +1,56 @@
package ca.uhn.fhir.jpa.demo;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.to.FhirTesterMvcConfig;
import ca.uhn.fhir.to.TesterConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
//@formatter:off
/**
* This spring config file configures the web testing module. It serves two
* purposes:
* 1. It imports FhirTesterMvcConfig, which is the spring config for the
* tester itself
* 2. It tells the tester which server(s) to talk to, via the testerConfig()
* method below
*/
@Configuration
@Import(FhirTesterMvcConfig.class)
public class FhirTesterConfigDstu2 {
/**
* This bean tells the testing webpage which servers it should configure itself
* to communicate with. In this example we configure it to talk to the local
* server, as well as one public server. If you are creating a project to
* deploy somewhere else, you might choose to only put your own server's
* address here.
*
* Note the use of the ${serverBase} variable below. This will be replaced with
* the base URL as reported by the server itself. Often for a simple Tomcat
* (or other container) installation, this will end up being something
* like "http://localhost:8080/hapi-fhir-jpaserver-example". If you are
* deploying your server to a place with a fully qualified domain name,
* you might want to use that instead of using the variable.
*/
@Bean
public TesterConfig testerConfig() {
TesterConfig retVal = new TesterConfig();
retVal
.addServer()
.withId("home")
.withFhirVersion(FhirVersionEnum.DSTU2)
.withBaseUrl("${serverBase}/baseDstu2")
.withName("Local Tester")
.addServer()
.withId("hapi")
.withFhirVersion(FhirVersionEnum.DSTU2)
.withBaseUrl("http://fhirtest.uhn.ca/baseDstu2")
.withName("Public HAPI Test Server");
return retVal;
}
}
//@formatter:on

View File

@ -0,0 +1,169 @@
package ca.uhn.fhir.jpa.demo;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu1;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Meta;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.ServletException;
import java.util.Collection;
import java.util.List;
public class JpaServerDemoDstu2 extends RestfulServer {
private static final long serialVersionUID = 1L;
private WebApplicationContext myAppCtx;
@SuppressWarnings("unchecked")
@Override
protected void initialize() throws ServletException {
super.initialize();
/*
* We want to support FHIR DSTU2 format. This means that the server
* will use the DSTU2 bundle format and other DSTU2 encoding changes.
*
* If you want to use DSTU1 instead, change the following line, and change the 2 occurrences of dstu2 in web.xml to dstu1
*/
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2;
setFhirContext(new FhirContext(fhirVersion));
// Get the spring context from the web container (it's declared in web.xml)
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
/*
* The BaseJavaConfigDstu2.java class is a spring configuration
* file which is automatically generated as a part of hapi-fhir-jpaserver-base and
* contains bean definitions for a resource provider for each resource type
*/
String resourceProviderBeanName;
if (fhirVersion == FhirVersionEnum.DSTU1) {
resourceProviderBeanName = "myResourceProvidersDstu1";
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
resourceProviderBeanName = "myResourceProvidersDstu2";
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
resourceProviderBeanName = "myResourceProvidersDstu3";
} else {
throw new IllegalStateException();
}
List<IResourceProvider> beans = myAppCtx.getBean(resourceProviderBeanName, List.class);
setResourceProviders(beans);
/*
* The system provider implements non-resource-type methods, such as
* transaction, and global history.
*/
Object systemProvider;
if (fhirVersion == FhirVersionEnum.DSTU1) {
systemProvider = myAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class);
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
systemProvider = myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
systemProvider = myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class);
} else {
throw new IllegalStateException();
}
setPlainProviders(systemProvider);
/*
* The conformance provider exports the supported resources, search parameters, etc for
* this server. The JPA version adds resource counts to the exported statement, so it
* is a nice addition.
*/
if (fhirVersion == FhirVersionEnum.DSTU1) {
IFhirSystemDao<List<IResource>, MetaDt> systemDao = myAppCtx.getBean("mySystemDaoDstu1", IFhirSystemDao.class);
JpaConformanceProviderDstu1 confProvider = new JpaConformanceProviderDstu1(this, systemDao);
confProvider.setImplementationDescription("Example Server");
setServerConformanceProvider(confProvider);
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
IFhirSystemDao<ca.uhn.fhir.model.dstu2.resource.Bundle, MetaDt> systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao,
myAppCtx.getBean(DaoConfig.class));
confProvider.setImplementationDescription("Example Server");
setServerConformanceProvider(confProvider);
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
IFhirSystemDao<Bundle, Meta> systemDao = myAppCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class);
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao,
myAppCtx.getBean(DaoConfig.class));
confProvider.setImplementationDescription("Example Server");
setServerConformanceProvider(confProvider);
} else {
throw new IllegalStateException();
}
/*
* Enable ETag Support (this is already the default)
*/
setETagSupport(ETagSupportEnum.ENABLED);
/*
* This server tries to dynamically generate narratives
*/
FhirContext ctx = getFhirContext();
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
/*
* Default to JSON and pretty printing
*/
setDefaultPrettyPrint(true);
setDefaultResponseEncoding(EncodingEnum.JSON);
/*
* -- New in HAPI FHIR 1.5 --
* This configures the server to page search results to and from
* the database, instead of only paging them to memory. This may mean
* a performance hit when performing searches that return lots of results,
* but makes the server much more scalable.
*/
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
*/
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
for (IServerInterceptor interceptor : interceptorBeans) {
this.registerInterceptor(interceptor);
}
/*
* If you are hosting this server at a specific DNS name, the server will try to
* figure out the FHIR base URL based on what the web container tells it, but
* this doesn't always work. If you are setting links in your search bundles that
* just refer to "localhost", you might want to use a server address strategy:
*/
//setServerAddressStrategy(new HardcodedServerAddressStrategy("http://mydomain.com/fhir/baseDstu2"));
/*
* If you are using DSTU3+, you may want to add a terminology uploader, which allows
* uploading of external terminologies such as Snomed CT. Note that this uploader
* does not have any security attached (any anonymous user may use it by default)
* so it is a potential security vulnerability. Consider using an AuthorizationInterceptor
* with this feature.
*/
//if (fhirVersion == FhirVersionEnum.DSTU3) {
// registerProvider(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class));
//}
}
}

View File

@ -1,5 +1,5 @@
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0" <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd"> xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd">
<listener> <listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
@ -13,7 +13,7 @@
<context-param> <context-param>
<param-name>contextConfigLocation</param-name> <param-name>contextConfigLocation</param-name>
<param-value> <param-value>
ca.uhn.fhir.jpa.demo.FhirServerConfigWSocket ca.uhn.fhir.jpa.demo.FhirServerConfigDstu2
</param-value> </param-value>
</context-param> </context-param>
@ -28,7 +28,7 @@
</init-param> </init-param>
<init-param> <init-param>
<param-name>contextConfigLocation</param-name> <param-name>contextConfigLocation</param-name>
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfig</param-value> <param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfigDstu2</param-value>
</init-param> </init-param>
<load-on-startup>2</load-on-startup> <load-on-startup>2</load-on-startup>
</servlet> </servlet>
@ -103,6 +103,4 @@
<filter-name>CORS Filter</filter-name> <filter-name>CORS Filter</filter-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</filter-mapping> </filter-mapping>
</web-app> </web-app>

View File

@ -1,108 +0,0 @@
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
ca.uhn.fhir.jpa.demo.FhirServerConfigDstu3WSocket
</param-value>
</context-param>
<!-- Servlets -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfig</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet>
<servlet-name>fhirServlet</servlet-name>
<servlet-class>ca.uhn.fhir.jpa.demo.JpaServerDemo</servlet-class>
<init-param>
<param-name>ImplementationDescription</param-name>
<param-value>FHIR JPA Server</param-value>
</init-param>
<init-param>
<param-name>FhirVersion</param-name>
<param-value>DSTU3</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>fhirServlet</servlet-name>
<url-pattern>/baseDstu3/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
<filter>
<filter-name>CORS Filter</filter-name>
<filter-class>org.ebaysf.web.cors.CORSFilter</filter-class>
<init-param>
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
</init-param>
<init-param>
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
<param-name>cors.allowed.headers</param-name>
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
<param-name>cors.exposed.headers</param-name>
<param-value>Location,Content-Location</param-value>
</init-param>
<init-param>
<description>A flag that suggests if CORS is supported with cookies</description>
<param-name>cors.support.credentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>A flag to control logging</description>
<param-name>cors.logging.enabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
<param-name>cors.preflight.maxage</param-name>
<param-value>300</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -1,108 +0,0 @@
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
ca.uhn.fhir.jpa.demo.FhirServerConfig
</param-value>
</context-param>
<!-- Servlets -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfig</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet>
<servlet-name>fhirServlet</servlet-name>
<servlet-class>ca.uhn.fhir.jpa.demo.JpaServerDemo</servlet-class>
<init-param>
<param-name>ImplementationDescription</param-name>
<param-value>FHIR JPA Server</param-value>
</init-param>
<init-param>
<param-name>FhirVersion</param-name>
<param-value>DSTU2</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>fhirServlet</servlet-name>
<url-pattern>/baseDstu2/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
<filter>
<filter-name>CORS Filter</filter-name>
<filter-class>org.ebaysf.web.cors.CORSFilter</filter-class>
<init-param>
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
</init-param>
<init-param>
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
<param-name>cors.allowed.headers</param-name>
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
<param-name>cors.exposed.headers</param-name>
<param-value>Location,Content-Location</param-value>
</init-param>
<init-param>
<description>A flag that suggests if CORS is supported with cookies</description>
<param-name>cors.support.credentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>A flag to control logging</description>
<param-name>cors.logging.enabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
<param-name>cors.preflight.maxage</param-name>
<param-value>300</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -66,6 +66,16 @@
Prevent duplicates in $everything query response in JPA server. Thanks to @vlad-ignatov Prevent duplicates in $everything query response in JPA server. Thanks to @vlad-ignatov
for reporting! for reporting!
</action> </action>
<action type="fix">
Fix issue in calling JPA server transactions programmatically where resources
are linked by object reference and not by ID where indexes were not correctly
generated. This should not affect most users.
</action>
<action type="fix" issue="678">
FIx issue in SubscriptionInterceptor that caused interceptor to only
actually notify listeners of the first 10 subscriptions. Thanks to Jeff Chung
for the pull request!
</action>
</release> </release>
<release version="2.5" date="2017-06-08"> <release version="2.5" date="2017-06-08">
<action type="fix"> <action type="fix">