Merge branch 'master' of https://github.com/jamesagnew/hapi-fhir into validate-subscription-criteria

This commit is contained in:
Jeff Chung 2017-06-19 14:13:50 -07:00
commit 0b578e3b0b
21 changed files with 1276 additions and 222 deletions

View File

@ -1,5 +1,6 @@
# Use docker-based build environment (instead of openvz)
sudo: false
#sudo: false
sudo: required
language: java
jdk:
@ -21,4 +22,4 @@ before_script:
script:
# - mvn -e -B clean install && cd hapi-fhir-ra && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID clean test jacoco:report coveralls:report
# - mvn -Dci=true -e -B -P ALLMODULES,NOPARALLEL,ERRORPRONE clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report
- mvn -Dci=true -e -B -P ALLMODULES,NOPARALLEL clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report
- mvn -Dci=true -e -B -P ALLMODULES,MINPARALLEL clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report

View File

@ -0,0 +1,90 @@
package ca.uhn.fhir.rest.client.interceptor;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.rest.client.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
/**
* This interceptor adds an arbitrary header to requests made by this client. Both the
* header name and the header value are specified by the calling code.
*/
public class SimpleRequestHeaderInterceptor implements IClientInterceptor {
private String myHeaderName;
private String myHeaderValue;
/**
* Constructor
*/
public SimpleRequestHeaderInterceptor() {
this(null, null);
}
/**
* Constructor
*/
public SimpleRequestHeaderInterceptor(String theHeaderName, String theHeaderValue) {
super();
myHeaderName = theHeaderName;
myHeaderValue = theHeaderValue;
}
public String getHeaderName() {
return myHeaderName;
}
public String getHeaderValue() {
return myHeaderValue;
}
@Override
public void interceptRequest(IHttpRequest theRequest) {
if (isNotBlank(getHeaderName())) {
theRequest.addHeader(getHeaderName(), getHeaderValue());
}
}
@Override
public void interceptResponse(IHttpResponse theResponse) throws IOException {
// nothing
}
public void setHeaderName(String theHeaderName) {
myHeaderName = theHeaderName;
}
public void setHeaderValue(String theHeaderValue) {
myHeaderValue = theHeaderValue;
}
}

View File

@ -100,6 +100,11 @@
<artifactId>thymeleaf</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
@ -627,13 +632,21 @@
</plugins>
</build>
</profile>
<!-- <profile> <id>DIST</id> <build> <plugins> <plugin> <groupId>de.juplo</groupId> <artifactId>hibernate4-maven-plugin</artifactId> <configuration> <force>true</force> <target>SCRIPT</target> <skip>${skip-hib4}</skip>
</configuration> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate_version}</version> </dependency> </dependencies> <executions>
<execution> <id>o10g</id> <goals> <goal>export</goal> </goals> <phase>test</phase> <configuration> <hibernateDialect>org.hibernate.dialect.Oracle10gDialect</hibernateDialect> <outputFile>${project.build.directory}/schema_oracle_10g.sql</outputFile>
</configuration> </execution> <execution> <id>derby</id> <goals> <goal>export</goal> </goals> <phase>test</phase> <configuration> <hibernateDialect>org.hibernate.dialect.DerbyTenSevenDialect</hibernateDialect>
<outputFile>${project.build.directory}/schema_derby.sql</outputFile> </configuration> </execution> <execution> <id>hsql</id> <goals> <goal>export</goal> </goals> <phase>test</phase> <configuration> <hibernateDialect>org.hibernate.dialect.HSQLDialect</hibernateDialect>
<outputFile>${project.build.directory}/schema_hsql.sql</outputFile> </configuration> </execution> <execution> <id>mysql5</id> <goals> <goal>export</goal> </goals> <phase>test</phase> <configuration> <hibernateDialect>org.hibernate.dialect.MySQL5Dialect</hibernateDialect>
<outputFile>${project.build.directory}/schema_mysql_5.sql</outputFile> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> -->
</profiles>
<profile>
<id>MINPARALLEL</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkCount>2</forkCount>
<reuseForks>true</reuseForks>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -33,6 +33,7 @@ import javax.persistence.criteria.Root;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
@ -40,8 +41,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
import ca.uhn.fhir.jpa.dao.data.*;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.ReindexFailureException;
@ -87,30 +87,33 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
return retVal;
}
@Autowired
private IResourceTableDao myResourceTableDao;
private int doPerformReindexingPassForResources(final Integer theCount, TransactionTemplate txTemplate) {
return txTemplate.execute(new TransactionCallback<Integer>() {
@SuppressWarnings("unchecked")
@Override
public Integer doInTransaction(TransactionStatus theStatus) {
TypedQuery<ResourceTable> q = myEntityManager.createQuery("SELECT t FROM " + ResourceTable.class.getSimpleName() + " t WHERE t.myIndexStatus IS null", ResourceTable.class);
int maxResult = 500;
if (theCount != null) {
maxResult = Math.min(theCount, 2000);
}
maxResult = Math.max(maxResult, 10);
TypedQuery<Long> q = myEntityManager.createQuery("SELECT t.myId FROM ResourceTable t WHERE t.myIndexStatus IS NULL", Long.class);
ourLog.debug("Beginning indexing query with maximum {}", maxResult);
q.setMaxResults(maxResult);
List<ResourceTable> resources = q.getResultList();
if (resources.isEmpty()) {
return 0;
}
ourLog.info("Indexing {} resources", resources.size());
Collection<Long> resources = q.getResultList();
int count = 0;
long start = System.currentTimeMillis();
for (ResourceTable resourceTable : resources) {
for (Long nextId : resources) {
ResourceTable resourceTable = myResourceTableDao.findOne(nextId);
try {
/*
* This part is because from HAPI 1.5 - 1.6 we changed the format of forced ID to be "type/id" instead of just "id"
@ -135,13 +138,22 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
throw new ReindexFailureException(resourceTable.getId());
}
count++;
if (count >= maxResult) {
break;
}
}
long delay = System.currentTimeMillis() - start;
long avg = (delay / resources.size());
ourLog.info("Indexed {} / {} resources in {}ms - Avg {}ms / resource", new Object[] { count, resources.size(), delay, avg });
return resources.size();
long avg;
if (count > 0) {
avg = (delay / count);
ourLog.info("Indexed {} resources in {}ms - Avg {}ms / resource", new Object[] { count, delay, avg });
} else {
ourLog.debug("Indexed 0 resources in {}ms", delay);
}
return count;
}
});
}
@ -158,6 +170,7 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
return retVal;
}
@Transactional(propagation = Propagation.REQUIRED, readOnly=true)
@Override
public Map<String, Long> getResourceCounts() {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();

View File

@ -64,21 +64,21 @@ public class DaoConfig {
*/
public static final Long DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS = DateUtils.MILLIS_PER_MINUTE;
// ***
// update setter javadoc if default changes
// ***
/**
* update setter javadoc if default changes
*/
private boolean myAllowExternalReferences = false;
// ***
// update setter javadoc if default changes
// ***
/**
* update setter javadoc if default changes
*/
private boolean myAllowInlineMatchUrlReferences = true;
private boolean myAllowMultipleDelete;
private boolean myDefaultSearchParamsCanBeOverridden = false;
// ***
// update setter javadoc if default changes
// ***
/**
* update setter javadoc if default changes
*/
private int myDeferIndexingForCodesystemsOfSize = 2000;
private boolean myDeleteStaleSearches = true;
@ -87,27 +87,35 @@ public class DaoConfig {
private boolean myEnforceReferentialIntegrityOnWrite = true;
// ***
// update setter javadoc if default changes
// ***
private int myEverythingIncludesFetchPageSize = 50;
/**
* update setter javadoc if default changes
*/
private long myExpireSearchResultsAfterMillis = DateUtils.MILLIS_PER_HOUR;
private int myHardTagListLimit = 1000;
/**
* update setter javadoc if default changes
*/
private Integer myFetchSizeDefaultMaximum = null;
private int myHardTagListLimit = 1000;
private int myIncludeLimit = 2000;
// ***
// update setter javadoc if default changes
// ***
/**
* update setter javadoc if default changes
*/
private boolean myIndexContainedResources = true;
private List<IServerInterceptor> myInterceptors;
// ***
// update setter javadoc if default changes
// ***
/**
* update setter javadoc if default changes
*/
private int myMaximumExpansionSize = 5000;
private int myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION;
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
private Long myReuseCachedSearchResultsForMillis = DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS;
private boolean mySchedulingDisabled;
private boolean mySubscriptionEnabled;
/**
* update setter javadoc if default changes
*/
private long mySubscriptionPollDelay = 1000;
private Long mySubscriptionPurgeInactiveAfterMillis;
private boolean mySuppressUpdatesWithNoChange = true;
@ -140,6 +148,22 @@ public class DaoConfig {
return myDeferIndexingForCodesystemsOfSize;
}
/**
* Unlike with normal search queries, $everything queries have their _includes loaded by the main search thread and these included results
* are added to the normal search results instead of being added on as extras in a page. This means that they will not appear multiple times
* as the search results are paged over.
* <p>
* In order to recursively load _includes, we process the original results in batches of this size. Adjust with caution, increasing this
* value may improve performance but may also cause memory issues.
* </p>
* <p>
* The default value is 50
* </p>
*/
public int getEverythingIncludesFetchPageSize() {
return myEverythingIncludesFetchPageSize;
}
/**
* Sets the number of milliseconds that search results for a given client search
* should be preserved before being purged from the database.
@ -160,6 +184,20 @@ public class DaoConfig {
return myExpireSearchResultsAfterMillis;
}
/**
* Gets the default maximum number of results to load in a query.
* <p>
* For example, if the database has a million Patient resources in it, and
* the client requests <code>GET /Patient</code>, if this value is set
* to a non-null value (default is <code>null</code>) only this number
* of results will be fetched. Setting this value appropriately
* can be useful to improve performance in some situations.
* </p>
*/
public Integer getFetchSizeDefaultMaximum() {
return myFetchSizeDefaultMaximum;
}
/**
* Gets the maximum number of results to return in a GetTags query (DSTU1 only)
*/
@ -516,6 +554,23 @@ public class DaoConfig {
myEnforceReferentialIntegrityOnWrite = theEnforceReferentialIntegrityOnWrite;
}
/**
* Unlike with normal search queries, $everything queries have their _includes loaded by the main search thread and these included results
* are added to the normal search results instead of being added on as extras in a page. This means that they will not appear multiple times
* as the search results are paged over.
* <p>
* In order to recursively load _includes, we process the original results in batches of this size. Adjust with caution, increasing this
* value may improve performance but may also cause memory issues.
* </p>
* <p>
* The default value is 50
* </p>
*/
public void setEverythingIncludesFetchPageSize(int theEverythingIncludesFetchPageSize) {
Validate.inclusiveBetween(1, Integer.MAX_VALUE, theEverythingIncludesFetchPageSize);
myEverythingIncludesFetchPageSize = theEverythingIncludesFetchPageSize;
}
/**
* If this is set to <code>false</code> (default is <code>true</code>) the stale search deletion
* task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION.
@ -549,6 +604,20 @@ public class DaoConfig {
myExpireSearchResultsAfterMillis = theExpireSearchResultsAfterMillis;
}
/**
* Gets the default maximum number of results to load in a query.
* <p>
* For example, if the database has a million Patient resources in it, and
* the client requests <code>GET /Patient</code>, if this value is set
* to a non-null value (default is <code>null</code>) only this number
* of results will be fetched. Setting this value appropriately
* can be useful to improve performance in some situations.
* </p>
*/
public void setFetchSizeDefaultMaximum(Integer theFetchSizeDefaultMaximum) {
myFetchSizeDefaultMaximum = theFetchSizeDefaultMaximum;
}
/**
* Do not call this method, it exists only for legacy reasons. It
* will be removed in a future version. Configure the page size on your

View File

@ -58,12 +58,15 @@ import javax.persistence.criteria.Subquery;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
@ -136,14 +139,18 @@ import ca.uhn.fhir.util.UrlUtil;
* searchs for resources
*/
public class SearchBuilder implements ISearchBuilder {
private static final List<Long> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<Long>());
private static Long NO_MORE = Long.valueOf(-1);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchBuilder.class);
private List<Long> myAlsoIncludePids;
private CriteriaBuilder myBuilder;
private BaseHapiFhirDao<?> myCallingDao;
private FhirContext myContext;
private EntityManager myEntityManager;
private IForcedIdDao myForcedIdDao;
private IFulltextSearchSvc myFulltextSearchSvc;
private Map<JoinKey, Join<?, ?>> myIndexJoins = Maps.newHashMap();
private SearchParameterMap myParams;
private ArrayList<Predicate> myPredicates;
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
@ -152,8 +159,8 @@ public class SearchBuilder implements ISearchBuilder {
private Root<ResourceTable> myResourceTableRoot;
private Class<? extends IBaseResource> myResourceType;
private ISearchParamRegistry mySearchParamRegistry;
private IHapiTerminologySvc myTerminologySvc;
private String mySearchUuid;
private IHapiTerminologySvc myTerminologySvc;
/**
* Constructor
@ -192,7 +199,7 @@ public class SearchBuilder implements ISearchBuilder {
private void addPredicateDate(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
Join<ResourceTable, ResourceIndexedSearchParamDate> join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
Join<ResourceTable, ResourceIndexedSearchParamDate> join = createOrReuseJoin(JoinEnum.DATE, theParamName);
if (theList.get(0).getMissing() != null) {
Boolean missing = theList.get(0).getMissing();
@ -268,28 +275,6 @@ public class SearchBuilder implements ISearchBuilder {
}
}
// private void addPredicateId(Set<Long> thePids) {
// if (thePids == null || thePids.isEmpty()) {
// return;
// }
//
// CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
// CriteriaQuery<Long> cq = builder.createQuery(Long.class);
// Root<ResourceTable> from = cq.from(ResourceTable.class);
// cq.select(from.get("myId").as(Long.class));
//
// List<Predicate> predicates = new ArrayList<Predicate>();
// predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
// predicates.add(from.get("myId").in(thePids));
// createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
// createPredicateLastUpdatedForResourceTable(builder, from, predicates);
//
// cq.where(toArray(predicates));
//
// TypedQuery<Long> q = myEntityManager.createQuery(cq);
// doSetPids(q.getResultList());
// }
private void addPredicateLanguage(List<List<? extends IQueryParameterType>> theList) {
for (List<? extends IQueryParameterType> nextList : theList) {
@ -319,7 +304,7 @@ public class SearchBuilder implements ISearchBuilder {
private void addPredicateNumber(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
Join<ResourceTable, ResourceIndexedSearchParamNumber> join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
Join<ResourceTable, ResourceIndexedSearchParamNumber> join = createOrReuseJoin(JoinEnum.NUMBER, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
@ -371,7 +356,7 @@ public class SearchBuilder implements ISearchBuilder {
}
private void addPredicateQuantity(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
Join<ResourceTable, ResourceIndexedSearchParamQuantity> join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
Join<ResourceTable, ResourceIndexedSearchParamQuantity> join = createOrReuseJoin(JoinEnum.QUANTITY, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
@ -399,7 +384,7 @@ public class SearchBuilder implements ISearchBuilder {
return;
}
Join<ResourceTable, ResourceLink> join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
Join<ResourceTable, ResourceLink> join = createOrReuseJoin(JoinEnum.REFERENCE, theParamName);
List<Predicate> codePredicates = new ArrayList<Predicate>();
@ -459,7 +444,7 @@ public class SearchBuilder implements ISearchBuilder {
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceName);
RuntimeSearchParam searchParamByName = myCallingDao.getSearchParamByName(resourceDef, theParamName);
if (searchParamByName == null) {
throw new InternalErrorException("Could not find parameter " + theParamName );
throw new InternalErrorException("Could not find parameter " + theParamName);
}
String paramPath = searchParamByName.getPath();
if (paramPath.endsWith(".as(Reference)")) {
@ -489,7 +474,7 @@ public class SearchBuilder implements ISearchBuilder {
if (resourceTypes.isEmpty()) {
for (BaseRuntimeElementDefinition<?> next : myContext.getElementDefinitions()) {
if (next instanceof RuntimeResourceDefinition) {
RuntimeResourceDefinition nextResDef = (RuntimeResourceDefinition)next;
RuntimeResourceDefinition nextResDef = (RuntimeResourceDefinition) next;
resourceTypes.add(nextResDef.getImplementingClass());
}
}
@ -575,8 +560,10 @@ public class SearchBuilder implements ISearchBuilder {
*/
Root<ResourceTable> stackRoot = myResourceTableRoot;
ArrayList<Predicate> stackPredicates = myPredicates;
Map<JoinKey, Join<?, ?>> stackIndexJoins = myIndexJoins;
myResourceTableRoot = subQfrom;
myPredicates = new ArrayList<Predicate>();
myPredicates = Lists.newArrayList();
myIndexJoins = Maps.newHashMap();
// Create the subquery predicates
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), subResourceName));
@ -590,6 +577,7 @@ public class SearchBuilder implements ISearchBuilder {
*/
myResourceTableRoot = stackRoot;
myPredicates = stackPredicates;
myIndexJoins = stackIndexJoins;
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join);
Predicate pidPredicate = join.get("myTargetResourcePid").in(subQ);
@ -654,7 +642,7 @@ public class SearchBuilder implements ISearchBuilder {
private void addPredicateString(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
Join<ResourceTable, ResourceIndexedSearchParamString> join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
Join<ResourceTable, ResourceIndexedSearchParamString> join = createOrReuseJoin(JoinEnum.STRING, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
@ -802,7 +790,7 @@ public class SearchBuilder implements ISearchBuilder {
private void addPredicateToken(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
Join<ResourceTable, ResourceIndexedSearchParamToken> join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createOrReuseJoin(JoinEnum.TOKEN, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
@ -834,7 +822,7 @@ public class SearchBuilder implements ISearchBuilder {
private void addPredicateUri(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
Join<ResourceTable, ResourceIndexedSearchParamUri> join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
Join<ResourceTable, ResourceIndexedSearchParamUri> join = createOrReuseJoin(JoinEnum.URI, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
@ -959,6 +947,42 @@ public class SearchBuilder implements ISearchBuilder {
return retVal;
}
@SuppressWarnings("unchecked")
private <T> Join<ResourceTable, T> createOrReuseJoin(JoinEnum theType, String theSearchParameterName) {
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
switch (theType) {
case DATE:
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
break;
case NUMBER:
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
break;
case QUANTITY:
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
break;
case REFERENCE:
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
break;
case STRING:
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
break;
case URI:
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
break;
case TOKEN:
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
break;
}
JoinKey key = new JoinKey(theSearchParameterName, theType);
if (!myIndexJoins.containsKey(key)) {
myIndexJoins.put(key, join);
}
return (Join<ResourceTable, T>) join;
}
private Predicate createPredicateDate(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamDate> theFrom) {
Predicate p;
if (theParam instanceof DateParam) {
@ -1095,7 +1119,7 @@ public class SearchBuilder implements ISearchBuilder {
if (!isBlank(unitsValue)) {
code = theBuilder.equal(theFrom.get("myUnits"), unitsValue);
}
cmpValue = ObjectUtils.defaultIfNull(cmpValue, ParamPrefixEnum.EQUAL);
final Expression<BigDecimal> path = theFrom.get("myValue");
String invalidMessageName = "invalidQuantityPrefix";
@ -1238,10 +1262,10 @@ public class SearchBuilder implements ISearchBuilder {
// Use "in" in case of large numbers of codes due to param modifiers
final Path<String> systemExpression = theFrom.get("mySystem");
final Path<String> valueExpression = theFrom.get("myValue");
for (Map.Entry<String, List<VersionIndependentConcept>> entry: map.entrySet()) {
for (Map.Entry<String, List<VersionIndependentConcept>> entry : map.entrySet()) {
Predicate systemPredicate = theBuilder.equal(systemExpression, entry.getKey());
In<String> codePredicate = theBuilder.in(valueExpression);
for (VersionIndependentConcept nextCode: entry.getValue()) {
for (VersionIndependentConcept nextCode : entry.getValue()) {
codePredicate.value(nextCode.getCode());
}
orPredicates.add(theBuilder.and(systemPredicate, codePredicate));
@ -1291,9 +1315,7 @@ public class SearchBuilder implements ISearchBuilder {
return new QueryIterator();
}
private List<Long> myAlsoIncludePids;
private TypedQuery<Long> createQuery(SortSpec sort) {
private TypedQuery<Long> createQuery(SortSpec sort, Integer theMaximumResults) {
CriteriaQuery<Long> outerQuery;
/*
* Sort
@ -1337,16 +1359,7 @@ public class SearchBuilder implements ISearchBuilder {
}
myResourceTableQuery.distinct(true);
myPredicates = new ArrayList<Predicate>();
if (myParams.getEverythingMode() == null) {
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName));
}
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
DateRangeParam lu = myParams.getLastUpdated();
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lu, myBuilder, myResourceTableRoot);
myPredicates.addAll(lastUpdatedPredicates);
if (myParams.getEverythingMode() != null) {
Join<ResourceTable, ResourceLink> join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
@ -1390,12 +1403,36 @@ public class SearchBuilder implements ISearchBuilder {
myPredicates.add(myResourceTableRoot.get("myId").as(Long.class).in(pids));
}
/*
* Add a predicate to make sure we only include non-deleted resources, and only include
* resources of the right type.
*
* If we have any joins to index tables, we get this behaviour already guaranteed so we don't
* need an explicit predicate for it.
*/
if (myIndexJoins.isEmpty()) {
if (myParams.getEverythingMode() == null) {
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName));
}
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
}
// Last updated
DateRangeParam lu = myParams.getLastUpdated();
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lu, myBuilder, myResourceTableRoot);
myPredicates.addAll(lastUpdatedPredicates);
myResourceTableQuery.where(myBuilder.and(SearchBuilder.toArray(myPredicates)));
/*
* Now perform the search
*/
final TypedQuery<Long> query = myEntityManager.createQuery(outerQuery);
if (theMaximumResults != null) {
query.setMaxResults(theMaximumResults);
}
return query;
}
@ -1443,47 +1480,65 @@ public class SearchBuilder implements ISearchBuilder {
String joinAttrName;
String[] sortAttrName;
JoinEnum joinType;
switch (param.getParamType()) {
case STRING:
joinAttrName = "myParamsString";
sortAttrName = new String[] { "myValueExact" };
joinType = JoinEnum.STRING;
break;
case DATE:
joinAttrName = "myParamsDate";
sortAttrName = new String[] { "myValueLow" };
joinType = JoinEnum.DATE;
break;
case REFERENCE:
joinAttrName = "myResourceLinks";
sortAttrName = new String[] { "myTargetResourcePid" };
joinType = JoinEnum.REFERENCE;
break;
case TOKEN:
joinAttrName = "myParamsToken";
sortAttrName = new String[] { "mySystem", "myValue" };
joinType = JoinEnum.TOKEN;
break;
case NUMBER:
joinAttrName = "myParamsNumber";
sortAttrName = new String[] { "myValue" };
joinType = JoinEnum.NUMBER;
break;
case URI:
joinAttrName = "myParamsUri";
sortAttrName = new String[] { "myUri" };
joinType = JoinEnum.URI;
break;
case QUANTITY:
joinAttrName = "myParamsQuantity";
sortAttrName = new String[] { "myValue" };
joinType = JoinEnum.QUANTITY;
break;
default:
throw new InvalidRequestException("This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + theSort.getParamName());
}
From<?, ?> join = theFrom.join(joinAttrName, JoinType.LEFT);
/*
* If we've already got a join for the specific parameter we're
* sorting on, we'll also sort with it. Otherwise we need a new join.
*/
JoinKey key = new JoinKey(theSort.getParamName(), joinType);
Join<?, ?> join = myIndexJoins.get(key);
if (join == null) {
join = theFrom.join(joinAttrName, JoinType.LEFT);
if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
thePredicates.add(join.get("mySourcePath").as(String.class).in(param.getPathsSplit()));
if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
thePredicates.add(join.get("mySourcePath").as(String.class).in(param.getPathsSplit()));
} else {
Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName());
thePredicates.add(joinParam1);
}
} else {
Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName());
thePredicates.add(joinParam1);
ourLog.info("Reusing join for {}", theSort.getParamName());
}
for (String next : sortAttrName) {
@ -1529,41 +1584,6 @@ public class SearchBuilder implements ISearchBuilder {
return retVal;
}
@Override
public void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation,
EntityManager entityManager, FhirContext context, IDao theDao) {
if (theIncludePids.isEmpty()) {
ourLog.info("The include pids are empty");
//return;
}
// Dupes will cause a crash later anyhow, but this is expensive so only do it
// when running asserts
assert new HashSet<Long>(theIncludePids).size() == theIncludePids.size() : "PID list contains duplicates: " + theIncludePids;
Map<Long, Integer> position = new HashMap<Long, Integer>();
for (Long next : theIncludePids) {
position.put(next, theResourceListToPopulate.size());
theResourceListToPopulate.add(null);
}
/*
* As always, Oracle can't handle things that other databases don't mind.. In this
* case it doesn't like more than ~1000 IDs in a single load, so we break this up
* if it's lots of IDs. I suppose maybe we should be doing this as a join anyhow
* but this should work too. Sigh.
*/
int maxLoad = 800;
List<Long> pids = new ArrayList<Long>(theIncludePids);
for (int i = 0; i < pids.size(); i += maxLoad) {
int to = i + maxLoad;
to = Math.min(to, pids.size());
List<Long> pidsSubList = pids.subList(i, to);
doLoadPids(theResourceListToPopulate, theRevIncludedPids, theForHistoryOperation, entityManager, context, theDao, position, pidsSubList);
}
}
private void doLoadPids(List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager entityManager, FhirContext context, IDao theDao,
Map<Long, Integer> position, Collection<Long> pids) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
@ -1599,6 +1619,41 @@ public class SearchBuilder implements ISearchBuilder {
}
}
@Override
public void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation,
EntityManager entityManager, FhirContext context, IDao theDao) {
if (theIncludePids.isEmpty()) {
ourLog.info("The include pids are empty");
// return;
}
// Dupes will cause a crash later anyhow, but this is expensive so only do it
// when running asserts
assert new HashSet<Long>(theIncludePids).size() == theIncludePids.size() : "PID list contains duplicates: " + theIncludePids;
Map<Long, Integer> position = new HashMap<Long, Integer>();
for (Long next : theIncludePids) {
position.put(next, theResourceListToPopulate.size());
theResourceListToPopulate.add(null);
}
/*
* As always, Oracle can't handle things that other databases don't mind.. In this
* case it doesn't like more than ~1000 IDs in a single load, so we break this up
* if it's lots of IDs. I suppose maybe we should be doing this as a join anyhow
* but this should work too. Sigh.
*/
int maxLoad = 800;
List<Long> pids = new ArrayList<Long>(theIncludePids);
for (int i = 0; i < pids.size(); i += maxLoad) {
int to = i + maxLoad;
to = Math.min(to, pids.size());
List<Long> pidsSubList = pids.subList(i, to);
doLoadPids(theResourceListToPopulate, theRevIncludedPids, theForHistoryOperation, entityManager, context, theDao, position, pidsSubList);
}
}
/**
* THIS SHOULD RETURN HASHSET and not jsut Set because we add to it later (so it can't be Collections.emptySet())
*
@ -1645,12 +1700,6 @@ public class SearchBuilder implements ISearchBuilder {
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
if (theReverseMode) {
// if (theEverythingModeEnum.isEncounter()) {
// if (resourceLink.getSourcePath().equals("Encounter.subject") ||
// resourceLink.getSourcePath().equals("Encounter.patient")) {
// nextRoundOmit.add(resourceLink.getSourceResourcePid());
// }
// }
pidsToInclude.add(resourceLink.getSourceResourcePid());
} else {
pidsToInclude.add(resourceLink.getTargetResourcePid());
@ -1761,10 +1810,10 @@ public class SearchBuilder implements ISearchBuilder {
}
private void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams) {
for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) {
List<? extends IQueryParameterType> nextOrList = theAndOrParams.get(andListIdx);
for (int orListIdx = 0; orListIdx < nextOrList.size(); orListIdx++) {
IQueryParameterType nextOr = nextOrList.get(orListIdx);
boolean hasNoValue = false;
@ -1776,14 +1825,14 @@ public class SearchBuilder implements ISearchBuilder {
hasNoValue = true;
}
}
if (hasNoValue) {
ourLog.debug("Ignoring empty parameter: {}", theParamName);
nextOrList.remove(orListIdx);
orListIdx--;
}
}
if (nextOrList.isEmpty()) {
theAndOrParams.remove(andListIdx);
andListIdx--;
@ -1793,7 +1842,7 @@ public class SearchBuilder implements ISearchBuilder {
if (theAndOrParams.isEmpty()) {
return;
}
if (theParamName.equals(BaseResource.SP_RES_ID)) {
addPredicateResourceId(theAndOrParams);
@ -1870,7 +1919,7 @@ public class SearchBuilder implements ISearchBuilder {
}
@Override
public void setType(Class<? extends IBaseResource> theResourceType, String theResourceName) {
public void setType(Class<? extends IBaseResource> theResourceType, String theResourceName) {
myResourceType = theResourceType;
myResourceName = theResourceName;
}
@ -1988,18 +2037,116 @@ public class SearchBuilder implements ISearchBuilder {
static Predicate[] toArray(List<Predicate> thePredicates) {
return thePredicates.toArray(new Predicate[thePredicates.size()]);
}
public class IncludesIterator implements Iterator<Long>{
private Iterator<Long> myCurrentIterator;
private int myCurrentOffset;
private ArrayList<Long> myCurrentPids;
private Long myNext;
private int myPageSize = myCallingDao.getConfig().getEverythingIncludesFetchPageSize();
public IncludesIterator(Set<Long> thePidSet) {
myCurrentPids = new ArrayList<Long>(thePidSet);
myCurrentIterator = EMPTY_LONG_LIST.iterator();
myCurrentOffset = 0;
}
private void fetchNext() {
while (myNext == null) {
if (myCurrentIterator.hasNext()) {
myNext = myCurrentIterator.next();
break;
}
if (!myCurrentIterator.hasNext()) {
int start = myCurrentOffset;
int end = myCurrentOffset + myPageSize;
if (end > myCurrentPids.size()) {
end = myCurrentPids.size();
}
if (end - start <= 0) {
myNext = NO_MORE;
break;
}
myCurrentOffset = end;
Collection<Long> pidsToScan = myCurrentPids.subList(start, end);
Set<Include> includes = Collections.singleton(new Include("*", true));
Set<Long> newPids = loadReverseIncludes(myCallingDao, myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated());
myCurrentIterator = newPids.iterator();
}
}
}
@Override
public boolean hasNext() {
fetchNext();
return myNext != NO_MORE;
}
@Override
public Long next() {
fetchNext();
Long retVal = myNext;
myNext = null;
return retVal;
}
}
private enum JoinEnum {
DATE, NUMBER, QUANTITY, REFERENCE, STRING, TOKEN, URI
}
private static class JoinKey {
private final JoinEnum myJoinType;
private final String myParamName;
public JoinKey(String theParamName, JoinEnum theJoinType) {
super();
myParamName = theParamName;
myJoinType = theJoinType;
}
@Override
public boolean equals(Object theObj) {
JoinKey obj = (JoinKey) theObj;
return new EqualsBuilder()
.append(myParamName, obj.myParamName)
.append(myJoinType, obj.myJoinType)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(myParamName)
.append(myJoinType)
.toHashCode();
}
}
private final class QueryIterator implements Iterator<Long> {
private boolean myFirst = true;
private IncludesIterator myIncludesIterator;
private Long myNext;
private final Set<Long> myPidSet = new HashSet<Long>();
private Iterator<Long> myPreResultsIterator;
private Iterator<Long> myResultsIterator;
private SortSpec mySort;
private Iterator<Long> myPreResultsIterator;
private boolean myFirst = true;
private boolean myStillNeedToFetchIncludes;
private StopWatch myStopwatch = null;
private QueryIterator() {
mySort = myParams.getSort();
// Includes are processed inline for $everything query
if (myParams.getEverythingMode() != null) {
myStillNeedToFetchIncludes = true;
}
}
private void fetchNext() {
@ -2010,7 +2157,9 @@ public class SearchBuilder implements ISearchBuilder {
// If we don't have a query yet, create one
if (myResultsIterator == null) {
final TypedQuery<Long> query = createQuery(mySort);
Integer maximumResults = myCallingDao.getConfig().getFetchSizeDefaultMaximum();
final TypedQuery<Long> query = createQuery(mySort, maximumResults);
myResultsIterator = query.getResultList().iterator();
// If the query resulted in extra results being requested
@ -2042,7 +2191,24 @@ public class SearchBuilder implements ISearchBuilder {
}
if (myNext == null) {
myNext = NO_MORE;
if (myStillNeedToFetchIncludes) {
myIncludesIterator = new IncludesIterator(myPidSet);
myStillNeedToFetchIncludes = false;
}
if (myIncludesIterator != null) {
while (myIncludesIterator.hasNext()) {
Long next = myIncludesIterator.next();
if (next != null && myPidSet.add(next)) {
myNext = next;
break;
}
}
if (myNext == null) {
myNext = NO_MORE;
}
} else {
myNext = NO_MORE;
}
}
} // if we need to fetch the next result
@ -2051,9 +2217,11 @@ public class SearchBuilder implements ISearchBuilder {
ourLog.info("Initial query result returned in {}ms for query {}", myStopwatch.getMillis(), mySearchUuid);
myFirst = false;
}
if (myNext == NO_MORE) {
ourLog.info("Query found {} matches in {}ms for query {}", myPidSet.size(), myStopwatch.getMillis(), mySearchUuid);
}
}
@Override

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.dao.data;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
/*
* #%L
* HAPI FHIR JPA Server
@ -19,10 +21,7 @@ package ca.uhn.fhir.jpa.dao.data;
* limitations under the License.
* #L%
*/
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import ca.uhn.fhir.jpa.entity.ResourceTable;
@ -33,4 +32,7 @@ public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
@Query("UPDATE ResourceTable r SET r.myIndexStatus = null WHERE r.myResourceType = :restype")
int markResourcesOfTypeAsRequiringReindexing(@Param("restype") String theResourceType);
@Query("SELECT t.myId FROM ResourceTable t WHERE t.myIndexStatus IS NULL")
Slice<Long> findUnindexed(Pageable thePageRequest);
}

View File

@ -39,6 +39,7 @@ import java.util.Map.Entry;
import java.util.Set;
import javax.persistence.TypedQuery;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.NameValuePair;
import org.hl7.fhir.dstu3.model.Bundle;
@ -79,6 +80,7 @@ import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
@ -282,7 +284,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
super.clearRequestAsProcessingSubRequest(theRequestDetails);
}
}
@SuppressWarnings("unchecked")
private Bundle doTransaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
BundleType transactionType = theRequest.getTypeElement().getValue();
@ -408,7 +410,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
String matchUrl = nextReqEntry.getRequest().getIfNoneExist();
outcome = resourceDao.create(res, matchUrl, false, theRequestDetails);
if (nextResourceId != null) {
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res);
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
}
entriesToProcess.put(nextRespEntry, outcome.getEntity());
if (outcome.getCreated() == false) {
@ -445,12 +447,12 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
if (allDeleted.isEmpty()) {
status = Constants.STATUS_HTTP_204_NO_CONTENT;
}
nextRespEntry.getResponse().setOutcome((Resource) deleteOutcome.getOperationOutcome());
}
nextRespEntry.getResponse().setStatus(toStatusString(status));
break;
}
case PUT: {
@ -474,7 +476,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
}
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res);
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
entriesToProcess.put(nextRespEntry, outcome.getEntity());
break;
}
@ -646,10 +648,8 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
return response;
}
private static void handleTransactionCreateOrUpdateOutcome(Map<IdType, IdType> idSubstitutions, Map<IdType, DaoMethodOutcome> idToPersistedOutcome, IdType nextResourceId, DaoMethodOutcome outcome,
BundleEntryComponent newEntry, String theResourceType, IBaseResource theRes) {
BundleEntryComponent newEntry, String theResourceType, IBaseResource theRes, ServletRequestDetails theRequestDetails) {
IdType newId = (IdType) outcome.getId().toUnqualifiedVersionless();
IdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
if (newId.equals(resourceId) == false) {
@ -668,6 +668,19 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
}
newEntry.getResponse().setLastModified(((Resource) theRes).getMeta().getLastUpdated());
if (theRequestDetails != null) {
if (outcome.getResource() != null) {
String prefer = theRequestDetails.getHeader(Constants.HEADER_PREFER);
PreferReturnEnum preferReturn = RestfulServerUtils.parsePreferHeader(prefer);
if (preferReturn != null) {
if (preferReturn == PreferReturnEnum.REPRESENTATION) {
newEntry.setResource((Resource) outcome.getResource());
}
}
}
}
}
private static boolean isPlaceholder(IdType theId) {

View File

@ -241,8 +241,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
Set<Long> includedPids = new HashSet<Long>();
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated()));
includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated()));
}
includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated()));
// Execute the query and make sure we return distinct results
List<IBaseResource> resources = new ArrayList<IBaseResource>();

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.config;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
@ -17,6 +18,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
@Configuration
@EnableTransactionManagement()
@ -34,7 +37,15 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
retVal.setUrl("jdbc:derby:memory:myUnitTestDB;create=true");
retVal.setUsername("");
retVal.setPassword("");
return retVal;
DataSource dataSource = ProxyDataSourceBuilder
.create(retVal)
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
.countQuery()
.build();
return dataSource;
}
@Bean()

View File

@ -81,8 +81,7 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
@ -130,6 +129,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
@Before
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum());
}
/**
@ -1181,6 +1181,23 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
assertThat(patients, (hasItems(id1a, id1b)));
assertThat(patients, not(hasItems(id2)));
}
{
SearchParameterMap params = new SearchParameterMap();
params.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, beforeR2)));
List<IIdType> patients = toUnqualifiedVersionlessIds(myPatientDao.search(params));
assertThat(patients, not(hasItems(id1a, id1b)));
assertThat(patients, (hasItems(id2)));
}
{
SearchParameterMap params = new SearchParameterMap();
params.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.LESSTHAN_OR_EQUALS, beforeR2)));
List<IIdType> patients = toUnqualifiedVersionlessIds(myPatientDao.search(params));
assertThat(patients, (hasItems(id1a, id1b)));
assertThat(patients, not(hasItems(id2)));
}
}
@SuppressWarnings("deprecation")
@ -1827,6 +1844,48 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
}
@Test
public void testSearchStringParamDoesntMatchWrongType() throws Exception {
IIdType pid1;
IIdType pid2;
{
Patient patient = new Patient();
patient.addName().setFamily("HELLO");
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
{
Practitioner patient = new Practitioner();
patient.addName().setFamily("HELLO");
pid2 = myPractitionerDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
SearchParameterMap params;
List<IIdType> patients;
params = new SearchParameterMap();
params.add(Patient.SP_FAMILY, new StringParam("HELLO"));
patients = toUnqualifiedVersionlessIds(myPatientDao.search(params));
assertThat(patients, containsInAnyOrder(pid1));
assertThat(patients, not(containsInAnyOrder(pid2)));
}
@Test
public void testSearchWithFetchSizeDefaultMaximum() {
myDaoConfig.setFetchSizeDefaultMaximum(5);
for (int i = 0; i < 10; i++) {
Patient p = new Patient();
p.addName().setFamily("PT" + i);
myPatientDao.create(p);
}
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
IBundleProvider values = myPatientDao.search(map);
assertEquals(5, values.size().intValue());
assertEquals(5, values.getResources(0, 1000).size());
}
@Test
public void testSearchStringParam() throws Exception {
IIdType pid1;
@ -3251,11 +3310,26 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
SearchParameterMap map;
List<String> ids;
// No search param
map = new SearchParameterMap();
map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC)));
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
// Same SP as sort
map = new SearchParameterMap();
map.add(Patient.SP_ACTIVE, new TokenParam(null, "true"));
map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC)));
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
// Different SP from sort
map = new SearchParameterMap();
map.add(Patient.SP_GENDER, new TokenParam(null, "male"));
map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC)));
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
map = new SearchParameterMap();
map.setSort(new SortSpec("gender").setChain(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC))));
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));

View File

@ -0,0 +1,254 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsInRelativeOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.*;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.*;
import org.apache.http.message.BasicNameValuePair;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Bundle.*;
import org.hl7.fhir.dstu3.model.Encounter.EncounterLocationComponent;
import org.hl7.fhir.dstu3.model.Encounter.EncounterStatus;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.Encounter.EncounterState;
import org.hl7.fhir.instance.model.api.*;
import org.junit.*;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
public class PatientEverythingDstu3Test extends BaseResourceProviderDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PatientEverythingDstu3Test.class);
private String orgId;
private String patId;
private String encId1;
private String encId2;
private ArrayList<String> myObsIds;
private String myWrongPatId;
private String myWrongEnc1;
@Before
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@Override
@After
public void after() throws Exception {
super.after();
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
myDaoConfig.setEverythingIncludesFetchPageSize(new DaoConfig().getEverythingIncludesFetchPageSize());
}
@Override
public void before() throws Exception {
super.before();
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
myDaoConfig.setAllowMultipleDelete(true);
Organization org = new Organization();
org.setName("an org");
orgId = ourClient.create().resource(org).execute().getId().toUnqualifiedVersionless().getValue();
ourLog.info("OrgId: {}", orgId);
Patient patient = new Patient();
patient.getManagingOrganization().setReference(orgId);
patId = ourClient.create().resource(patient).execute().getId().toUnqualifiedVersionless().getValue();
Patient patient2 = new Patient();
patient2.getManagingOrganization().setReference(orgId);
myWrongPatId = ourClient.create().resource(patient2).execute().getId().toUnqualifiedVersionless().getValue();
Encounter enc1 = new Encounter();
enc1.setStatus(EncounterStatus.CANCELLED);
enc1.getSubject().setReference(patId);
enc1.getServiceProvider().setReference(orgId);
encId1 = ourClient.create().resource(enc1).execute().getId().toUnqualifiedVersionless().getValue();
Encounter enc2 = new Encounter();
enc2.setStatus(EncounterStatus.ARRIVED);
enc2.getSubject().setReference(patId);
enc2.getServiceProvider().setReference(orgId);
encId2 = ourClient.create().resource(enc2).execute().getId().toUnqualifiedVersionless().getValue();
Encounter wrongEnc1 = new Encounter();
wrongEnc1.setStatus(EncounterStatus.ARRIVED);
wrongEnc1.getSubject().setReference(myWrongPatId);
wrongEnc1.getServiceProvider().setReference(orgId);
myWrongEnc1 = ourClient.create().resource(wrongEnc1).execute().getId().toUnqualifiedVersionless().getValue();
myObsIds = new ArrayList<String>();
for (int i = 0; i < 20; i++) {
Observation obs = new Observation();
obs.getSubject().setReference(patId);
obs.setStatus(ObservationStatus.FINAL);
String obsId = ourClient.create().resource(obs).execute().getId().toUnqualifiedVersionless().getValue();
myObsIds.add(obsId);
}
}
/**
* See #674
*/
@Test
public void testEverythingReturnsCorrectResources() throws Exception {
Bundle bundle = fetchBundle(ourServerBase + "/" + patId + "/$everything?_format=json&_count=100", EncodingEnum.JSON);
assertNull(bundle.getLink("next"));
Set<String> actual = new TreeSet<String>();
for (BundleEntryComponent nextEntry : bundle.getEntry()) {
actual.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
ourLog.info("Found IDs: {}", actual);
assertThat(actual, hasItem(patId));
assertThat(actual, hasItem(encId1));
assertThat(actual, hasItem(encId2));
assertThat(actual, hasItem(orgId));
assertThat(actual, hasItems(myObsIds.toArray(new String[0])));
assertThat(actual, not(hasItem(myWrongPatId)));
assertThat(actual, not(hasItem(myWrongEnc1)));
}
/**
* See #674
*/
@Test
public void testEverythingReturnsCorrectResourcesSmallPage() throws Exception {
myDaoConfig.setEverythingIncludesFetchPageSize(1);
Bundle bundle = fetchBundle(ourServerBase + "/" + patId + "/$everything?_format=json&_count=100", EncodingEnum.JSON);
assertNull(bundle.getLink("next"));
Set<String> actual = new TreeSet<String>();
for (BundleEntryComponent nextEntry : bundle.getEntry()) {
actual.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
ourLog.info("Found IDs: {}", actual);
assertThat(actual, hasItem(patId));
assertThat(actual, hasItem(encId1));
assertThat(actual, hasItem(encId2));
assertThat(actual, hasItem(orgId));
assertThat(actual, hasItems(myObsIds.toArray(new String[0])));
assertThat(actual, not(hasItem(myWrongPatId)));
assertThat(actual, not(hasItem(myWrongEnc1)));
}
/**
* See #674
*/
@Test
public void testEverythingPagesWithCorrectEncodingJson() throws Exception {
Bundle bundle = fetchBundle(ourServerBase + "/" + patId + "/$everything?_format=json&_count=1", EncodingEnum.JSON);
assertNotNull(bundle.getLink("next").getUrl());
assertThat(bundle.getLink("next").getUrl(), containsString("_format=json"));
bundle = fetchBundle(bundle.getLink("next").getUrl(), EncodingEnum.JSON);
assertNotNull(bundle.getLink("next").getUrl());
assertThat(bundle.getLink("next").getUrl(), containsString("_format=json"));
bundle = fetchBundle(bundle.getLink("next").getUrl(), EncodingEnum.JSON);
}
/**
* See #674
*/
@Test
public void testEverythingPagesWithCorrectEncodingXml() throws Exception {
Bundle bundle = fetchBundle(ourServerBase + "/" + patId + "/$everything?_format=xml&_count=1", EncodingEnum.XML);
assertNotNull(bundle.getLink("next").getUrl());
ourLog.info("Next link: {}", bundle.getLink("next").getUrl());
assertThat(bundle.getLink("next").getUrl(), containsString("_format=xml"));
bundle = fetchBundle(bundle.getLink("next").getUrl(), EncodingEnum.XML);
assertNotNull(bundle.getLink("next").getUrl());
ourLog.info("Next link: {}", bundle.getLink("next").getUrl());
assertThat(bundle.getLink("next").getUrl(), containsString("_format=xml"));
bundle = fetchBundle(bundle.getLink("next").getUrl(), EncodingEnum.XML);
}
private Bundle fetchBundle(String theUrl, EncodingEnum theEncoding) throws IOException, ClientProtocolException {
Bundle bundle;
HttpGet get = new HttpGet(theUrl);
CloseableHttpResponse resp = ourHttpClient.execute(get);
try {
assertEquals(theEncoding.getResourceContentTypeNonLegacy(), resp.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue().replaceAll(";.*", ""));
bundle = theEncoding.newParser(myFhirCtx).parseResource(Bundle.class, IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8));
} finally {
IOUtils.closeQuietly(resp);
}
return bundle;
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -10,6 +10,8 @@ import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.Matchers.stringContainsInOrder;
@ -45,6 +47,7 @@ import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.Encounter.EncounterState;
import org.hl7.fhir.instance.model.api.*;
import org.junit.*;
import org.springframework.transaction.TransactionStatus;
@ -1367,6 +1370,53 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info(ids.toString());
}
@Test
public void testSearchByLastUpdated() throws Exception {
String methodName = "testSearchByLastUpdated";
Patient p = new Patient();
p.addName().setFamily(methodName+"1");
IIdType pid1 = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
Thread.sleep(10);
long time1 = System.currentTimeMillis();
Thread.sleep(10);
Patient p2 = new Patient();
p2.addName().setFamily(methodName+"2");
IIdType pid2 = ourClient.create().resource(p2).execute().getId().toUnqualifiedVersionless();
HttpGet get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=lt" + new InstantType(new Date(time1)).getValueAsString());
CloseableHttpResponse response = ourHttpClient.execute(get);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(response.getEntity().getContent());
ourLog.info(output);
List<IIdType> ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output));
ourLog.info(ids.toString());
assertThat(ids, containsInAnyOrder(pid1));
} finally {
response.close();
}
get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=gt" + new InstantType(new Date(time1)).getValueAsString());
response = ourHttpClient.execute(get);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(response.getEntity().getContent());
ourLog.info(output);
List<IIdType> ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output));
ourLog.info(ids.toString());
assertThat(ids, containsInAnyOrder(pid2));
} finally {
response.close();
}
}
@Test
public void testEverythingPatientType() throws Exception {
String methodName = "testEverythingPatientType";
@ -1536,12 +1586,19 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(responseBundle));
assertEquals(22, responseBundle.getEntry().size());
List<String> ids = new ArrayList<String>();
for (BundleEntryComponent nextEntry : responseBundle.getEntry()) {
ids.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
Collections.sort(ids);
ourLog.info("{} ids: {}", ids.size(), ids);
assertThat(responseBundle.getEntry().size(), lessThanOrEqualTo(25));
TreeSet<String> ids = new TreeSet<String>();
TreeSet<String> idsSet = new TreeSet<String>();
for (int i = 0; i < responseBundle.getEntry().size(); i++) {
for (BundleEntryComponent nextEntry : responseBundle.getEntry()) {
ids.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
idsSet.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
}
@ -1549,7 +1606,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
responseBundle = ourClient.fetchResourceFromUrl(Bundle.class, nextUrl);
for (int i = 0; i < responseBundle.getEntry().size(); i++) {
for (BundleEntryComponent nextEntry : responseBundle.getEntry()) {
ids.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
idsSet.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
}
@ -1557,19 +1614,19 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
responseBundle = ourClient.fetchResourceFromUrl(Bundle.class, nextUrl);
for (int i = 0; i < responseBundle.getEntry().size(); i++) {
for (BundleEntryComponent nextEntry : responseBundle.getEntry()) {
ids.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
idsSet.add(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
}
assertEquals(null, responseBundle.getLink("next"));
assertThat(ids, hasItem("List/A161444"));
assertThat(ids, hasItem("List/A161468"));
assertThat(ids, hasItem("List/A161500"));
assertThat(idsSet, hasItem("List/A161444"));
assertThat(idsSet, hasItem("List/A161468"));
assertThat(idsSet, hasItem("List/A161500"));
ourLog.info("Expected {} - {}", allIds.size(), allIds);
ourLog.info("Actual {} - {}", ids.size(), ids);
assertEquals(allIds, ids);
ourLog.info("Actual {} - {}", idsSet.size(), idsSet);
assertEquals(allIds, idsSet);
}

View File

@ -60,6 +60,7 @@ import ca.uhn.fhir.jpa.rp.dstu3.OrganizationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
@ -80,6 +81,7 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderDstu3Test.class);
private static Server ourServer;
private static String ourServerBase;
private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor;
@Test
public void testTransactionWithInlineConditionalUrl() throws Exception {
@ -237,10 +239,17 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test {
myRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
}
@Before
public void before() {
mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor();
ourClient.registerInterceptor(mySimpleHeaderInterceptor);
}
@SuppressWarnings("deprecation")
@After
public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
}
@SuppressWarnings("deprecation")
@ -632,6 +641,44 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test {
assertEquals(0, respSub.getEntry().size());
}
@Test
public void testTransactionCreateWithPreferHeader() throws Exception {
Patient p = new Patient();
p.setActive(true);
Bundle req;
Bundle resp;
// No prefer header
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(null, resp.getEntry().get(0).getResource());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
// Prefer return=minimal
mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER);
mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_MINIMAL);
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(null, resp.getEntry().get(0).getResource());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
// Prefer return=representation
mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER);
mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_REPRESENTATION);
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(Patient.class, resp.getEntry().get(0).getResource().getClass());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();

View File

@ -46,6 +46,7 @@ import org.hl7.fhir.dstu3.model.CapabilityStatement.UnknownContentCode;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.After;
@ -56,8 +57,10 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import com.google.common.base.Charsets;
import com.google.common.collect.Sets;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
@ -81,14 +84,14 @@ public class JsonParserDstu3Test {
public void after() {
ourCtx.setNarrativeGenerator(null);
}
/**
* See #563
*/
@Test
public void testBadMessageForUnknownElement() throws IOException {
String input = IOUtils.toString(JsonParserDstu3Test.class.getResourceAsStream("/bad_parse_bundle_1.json"), StandardCharsets.UTF_8);
IParser p = ourCtx.newJsonParser();
p.setParserErrorHandler(new StrictErrorHandler());
try {
@ -99,14 +102,13 @@ public class JsonParserDstu3Test {
}
}
/**
* See #563
*/
@Test
public void testBadMessageForUnknownElement2() throws IOException {
String input = IOUtils.toString(JsonParserDstu3Test.class.getResourceAsStream("/bad_parse_bundle_2.json"), StandardCharsets.UTF_8);
IParser p = ourCtx.newJsonParser();
p.setParserErrorHandler(new StrictErrorHandler());
try {
@ -116,35 +118,35 @@ public class JsonParserDstu3Test {
assertEquals("Found incorrect type for element context - Expected OBJECT and found SCALAR (STRING)", e.getMessage());
}
}
/**
* See #544
*/
@Test
public void testBundleStitchReferencesByUuid() throws Exception {
Bundle bundle = new Bundle();
DocumentManifest dm = new DocumentManifest();
dm.getSubject().setReference("urn:uuid:96e85cca-9797-45d6-834a-c4eb27f331d3");
bundle.addEntry().setResource(dm);
Patient patient = new Patient();
patient.addName().setFamily("FAMILY");
bundle.addEntry().setResource(patient).setFullUrl("urn:uuid:96e85cca-9797-45d6-834a-c4eb27f331d3");
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle);
ourLog.info(encoded);
bundle = ourCtx.newJsonParser().parseResource(Bundle.class, encoded);
dm = (DocumentManifest) bundle.getEntry().get(0).getResource();
assertEquals("urn:uuid:96e85cca-9797-45d6-834a-c4eb27f331d3", dm.getSubject().getReference());
Patient subject = (Patient) dm.getSubject().getResource();
assertNotNull(subject);
assertEquals("FAMILY", subject.getNameFirstRep().getFamily());
}
/**
* Test for the url generated based on the server config
*/
@ -170,12 +172,12 @@ public class JsonParserDstu3Test {
newPatient = jsonParser.parseResource(MyPatientWithCustomUrlExtension.class, new StringReader(parsedPatient));
assertEquals("myName", newPatient.getPetName().getValue());
//Check no NPE if base server not configure
// Check no NPE if base server not configure
newPatient = ourCtx.newJsonParser().parseResource(MyPatientWithCustomUrlExtension.class, new StringReader(parsedPatient));
assertNull("myName", newPatient.getPetName().getValue());
assertEquals("myName", ((StringType) newPatient.getExtensionsByUrl("http://www.example.com/petname").get(0).getValue()).getValue());
}
@Test
public void testCustomUrlExtensioninBundle() {
final String expected = "{\"resourceType\":\"Bundle\",\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"extension\":[{\"url\":\"http://www.example.com/petname\",\"valueString\":\"myName\"}]}}]}";
@ -237,7 +239,6 @@ public class JsonParserDstu3Test {
assertEquals(3, countMatches(encoded, "resourceType"));
}
@Test
public void testEncodeAndParseExtensions() throws Exception {
@ -323,7 +324,7 @@ public class JsonParserDstu3Test {
assertEquals("CHILD", ((StringType) given2ext2.getValue()).getValue());
}
@Test
public void testEncodeAndParseMetaProfileAndTags() {
Patient p = new Patient();
@ -402,7 +403,6 @@ public class JsonParserDstu3Test {
assertEquals("sec_label2", tagList.get(1).getDisplay());
}
/**
* See #336
*/
@ -701,7 +701,7 @@ public class JsonParserDstu3Test {
.addExtension()
.setUrl("http://foo")
.setValue(new Reference("Practitioner/A"));
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
parser.setDontEncodeElements(new HashSet<String>(Arrays.asList("*.id", "*.meta")));
@ -1305,9 +1305,25 @@ public class JsonParserDstu3Test {
assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0000000000000001}}", str);
}
/**
* See #658
*/
@Test
public void testExtraElement() throws Exception {
IParser p = ourCtx.newJsonParser();
p.setParserErrorHandler(new StrictErrorHandler());
try {
p.parseResource(IOUtils.toString(JsonParserDstu3Test.class.getResourceAsStream("/Patient.json.txt"), Charsets.UTF_8));
fail();
} catch (DataFormatException e) {
assertEquals("Found incorrect type for element assigner - Expected OBJECT and found SCALAR (STRING)", e.getMessage());
}
}
@Test
public void testIncorrectJsonTypesIdAndArray() {
// ID should be a String and communication should be an Array
String input = "{\"resourceType\": \"Patient\",\n" +
" \"id\": 123,\n" +
@ -1320,35 +1336,35 @@ public class JsonParserDstu3Test {
"}";
IParser p = ourCtx.newJsonParser();
IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
p.setParserErrorHandler(errorHandler);
Patient patient = (Patient) p.parseResource(input);
ArgumentCaptor<String> elementName = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<ValueType> found = ArgumentCaptor.forClass(ValueType.class);
ArgumentCaptor<ValueType> expected = ArgumentCaptor.forClass(ValueType.class);
ArgumentCaptor<ScalarType> expectedScalarType = ArgumentCaptor.forClass(ScalarType.class);
ArgumentCaptor<ScalarType> foundScalarType = ArgumentCaptor.forClass(ScalarType.class);
verify(errorHandler, times(2)).incorrectJsonType(any(IParseLocation.class), elementName.capture(), expected.capture(), expectedScalarType.capture(), found.capture(), foundScalarType.capture());
assertEquals(ValueType.SCALAR, found.getAllValues().get(0));
assertEquals(ValueType.SCALAR, expected.getAllValues().get(0));
assertEquals(ScalarType.NUMBER, foundScalarType.getAllValues().get(0));
assertEquals(ScalarType.STRING, expectedScalarType.getAllValues().get(0));
assertEquals(ValueType.OBJECT, found.getAllValues().get(1));
assertEquals(ValueType.ARRAY, expected.getAllValues().get(1));
assertEquals(null, foundScalarType.getAllValues().get(1));
assertEquals(null, expectedScalarType.getAllValues().get(1));
assertEquals("123", patient.getIdElement().getIdPart());
assertEquals("Hindi", patient.getCommunicationFirstRep().getLanguage().getText());
}
@Test
public void testIncorrectJsonTypesNone() {
// ID should be a String and communication should be an Array
String input = "{\"resourceType\": \"Patient\",\n" +
" \"id\": \"123\",\n" +
@ -1361,18 +1377,18 @@ public class JsonParserDstu3Test {
"}";
IParser p = ourCtx.newJsonParser();
IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
p.setParserErrorHandler(errorHandler);
Patient patient = (Patient) p.parseResource(input);
ArgumentCaptor<String> elementName = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<ValueType> found = ArgumentCaptor.forClass(ValueType.class);
ArgumentCaptor<ValueType> expected = ArgumentCaptor.forClass(ValueType.class);
ArgumentCaptor<ScalarType> expectedScalarType = ArgumentCaptor.forClass(ScalarType.class);
ArgumentCaptor<ScalarType> foundScalarType = ArgumentCaptor.forClass(ScalarType.class);
verify(errorHandler, times(0)).incorrectJsonType(any(IParseLocation.class), elementName.capture(), expected.capture(), expectedScalarType.capture(), found.capture(), foundScalarType.capture());
assertEquals("123", patient.getIdElement().getIdPart());
assertEquals("Hindi", patient.getCommunicationFirstRep().getLanguage().getText());
}
@ -1380,21 +1396,21 @@ public class JsonParserDstu3Test {
@Test
public void testInvalidDateTimeValueInvalid() throws Exception {
IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
String res = "{ \"resourceType\": \"Observation\", \"valueDateTime\": \"foo\" }";
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(errorHandler);
Observation parsed = parser.parseResource(Observation.class, res);
assertEquals(null, parsed.getValueDateTimeType().getValue());
assertEquals("foo", parsed.getValueDateTimeType().getValueAsString());
ArgumentCaptor<String> msgCaptor = ArgumentCaptor.forClass(String.class);
verify(errorHandler, times(1)).invalidValue(isNull(IParseLocation.class), eq("foo"), msgCaptor.capture());
assertEquals("Invalid date/time format: \"foo\"", msgCaptor.getValue());
String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed);
assertEquals("{\"resourceType\":\"Observation\",\"valueDateTime\":\"foo\"}", encoded);
assertEquals("{\"resourceType\":\"Observation\",\"valueDateTime\":\"foo\"}", encoded);
}
/**
@ -1412,19 +1428,19 @@ public class JsonParserDstu3Test {
@Test
public void testInvalidEnumValueBlank() {
IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
String res = "{ \"resourceType\": \"Patient\", \"gender\": \"\" }";
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(errorHandler);
Patient parsed = parser.parseResource(Patient.class, res);
assertEquals(null, parsed.getGenderElement().getValue());
assertEquals(null, parsed.getGenderElement().getValueAsString());
ArgumentCaptor<String> msgCaptor = ArgumentCaptor.forClass(String.class);
verify(errorHandler, times(1)).invalidValue(isNull(IParseLocation.class), eq(""), msgCaptor.capture());
assertEquals("Attribute values must not be empty (\"\")", msgCaptor.getValue());
String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed);
assertEquals("{\"resourceType\":\"Patient\"}", encoded);
}
@ -1432,21 +1448,21 @@ public class JsonParserDstu3Test {
@Test
public void testInvalidEnumValueInvalid() {
IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
String res = "{ \"resourceType\": \"Patient\", \"gender\": \"foo\" }";
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(errorHandler);
Patient parsed = parser.parseResource(Patient.class, res);
assertEquals(null, parsed.getGenderElement().getValue());
assertEquals("foo", parsed.getGenderElement().getValueAsString());
ArgumentCaptor<String> msgCaptor = ArgumentCaptor.forClass(String.class);
verify(errorHandler, times(1)).invalidValue(isNull(IParseLocation.class), eq("foo"), msgCaptor.capture());
assertEquals("Unknown AdministrativeGender code 'foo'", msgCaptor.getValue());
String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed);
assertEquals("{\"resourceType\":\"Patient\",\"gender\":\"foo\"}", encoded);
assertEquals("{\"resourceType\":\"Patient\",\"gender\":\"foo\"}", encoded);
}
/**
@ -1910,10 +1926,10 @@ public class JsonParserDstu3Test {
public void testParseEmptyValue() {
String input = "{\"resourceType\":\"QuestionnaireResponse\",\"id\":\"123\",\"authored\":\"\",\"group\":{\"linkId\":\"\"}}";
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new LenientErrorHandler().setErrorOnInvalidValue(false));
QuestionnaireResponse qr = parser.parseResource(QuestionnaireResponse.class, input);
assertEquals("QuestionnaireResponse/123", qr.getIdElement().getValue());
assertEquals(null, qr.getAuthored());
assertEquals(null, qr.getAuthoredElement().getValue());
@ -2298,10 +2314,14 @@ public class JsonParserDstu3Test {
ArgumentCaptor<ValueType> actual = ArgumentCaptor.forClass(ValueType.class);
ArgumentCaptor<ScalarType> expectedScalar = ArgumentCaptor.forClass(ScalarType.class);
ArgumentCaptor<ScalarType> actualScalar = ArgumentCaptor.forClass(ScalarType.class);
verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.any(IParseLocation.class), elementName.capture(), expected.capture(), expectedScalar.capture(), actual.capture(), actualScalar.capture());
verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.any(IParseLocation.class), Mockito.eq("_id"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(), Mockito.eq(ValueType.SCALAR), actualScalar.capture());
verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.any(IParseLocation.class), Mockito.eq("__v"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(), Mockito.eq(ValueType.SCALAR), actualScalar.capture());
verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.any(IParseLocation.class), Mockito.eq("_status"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(), Mockito.eq(ValueType.SCALAR), actualScalar.capture());
verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.any(IParseLocation.class), elementName.capture(), expected.capture(), expectedScalar.capture(), actual.capture(),
actualScalar.capture());
verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.any(IParseLocation.class), Mockito.eq("_id"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(), Mockito.eq(ValueType.SCALAR),
actualScalar.capture());
verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.any(IParseLocation.class), Mockito.eq("__v"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(), Mockito.eq(ValueType.SCALAR),
actualScalar.capture());
verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.any(IParseLocation.class), Mockito.eq("_status"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(),
Mockito.eq(ValueType.SCALAR), actualScalar.capture());
assertEquals("_id", elementName.getAllValues().get(0));
assertEquals(ValueType.OBJECT, expected.getAllValues().get(0));

View File

@ -27,6 +27,7 @@ import org.apache.http.ProtocolVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentType;
@ -60,6 +61,7 @@ import ca.uhn.fhir.parser.CustomTypeDstu3Test.MyCustomPatient;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.client.SearchClientDstu3Test.ILocationClient;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.client.interceptor.CookieInterceptor;
@ -177,6 +179,8 @@ public class GenericClientDstu3Test {
OperationOutcome oo = (OperationOutcome) outcome.getOperationOutcome();
assertThat(oo.getText().getDivAsString(), containsString("OK!"));
}
@Test
public void testPatchJsonByIdType() throws Exception {
@ -1191,6 +1195,36 @@ public class GenericClientDstu3Test {
// assertEquals("FAM", resp.getNameFirstRep().getFamilyAsSingleString());
}
@Test
public void testSearchWithNullParameters() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
DateTimeDt now = DateTimeDt.withCurrentTime();
String dateString = now.getValueAsString().substring(0, 10);
client.search()
.forResource("Patient")
.where(Patient.NAME.matches().value((String)null))
.returnBundle(Bundle.class)
.execute();
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).getURI().toString());
idx++;
}
@Test
public void testSearchByDate() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";

View File

@ -43,7 +43,6 @@ import ca.uhn.fhir.rest.annotation.Sort;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.util.TestUtil;

View File

@ -0,0 +1,128 @@
{
"resourceType": "Patient",
"id": "patient1",
"contained": [{
"resourceType": "Practitioner",
"id": "p1",
"identifier": [{
"system": "urn:oid:2.16.840.1.113883.3.566",
"value": "145148",
"assigner": "UHC"
}],
"name": [{
"text": " Richard J Y Ha MD",
"given": ["Richard",
"J Y"],
"family": "Ha",
"suffix": ["MD"]
}]
}],
"identifier": [{
"use": "official",
"type": {
"coding": [{
"system": "http://hl7.org/fhir/identifier-type",
"code": "MR",
"display": "Medical Record Number",
"userSelected": true
}]
},
"system": "urn:oid:2.16.840.1.113883.3.566",
"value": "220457511",
"assigner": "MU"
},
{
"type": {
"coding": [{
"system": "http://hl7.org/fhir/identifier-type",
"code": "MR",
"display": "UHC ID",
"userSelected": true
}]
},
"system": "urn:oid:2.16.840.1.113883.3.566",
"value": "15246931",
"assigner": "UHC"
},
{
"type": {
"coding": [{
"system": "http://hl7.org/fhir/identifier-type",
"code": "MR",
"display": "ACCT NUM",
"userSelected": true
}]
},
"system": "urn:oid:2.16.840.1.113883.3.566",
"value": "226274321",
"assigner": "MU"
},
{
"type": {
"coding": [{
"system": "http://hl7.org/fhir/identifier-type",
"code": "MR",
"display": "HNAMPERSON_ID",
"userSelected": true
}]
},
"system": "urn:oid:2.16.840.1.113883.3.566",
"value": "25296343"
}],
"active": true,
"name": [{
"use": "official",
"text": " MOM TWO CERNER",
"given": ["MOM",
"TWO"],
"family": "CERNER"
}],
"gender": "female",
"birthDate": "1990-10-18",
"address": [{
"use": "home",
"text": "2401 LEMONE INDUSTRIAL BLVD, COLUMBIA, MO 65201 ",
"line": "2401 LEMONE INDUSTRIAL BLVD",
"city": "COLUMBIA",
"state": "MO",
"postalCode": "65201"
}],
"maritalStatus": {
"coding": [{
"system": "urn:oid:2.16.840.1.113883.3.566",
"code": "M",
"display": "Married",
"userSelected": true
}]
},
"contact": [{
"name": {
"text": "BABY TWO CERNER",
"given": ["BABY",
"TWO"],
"family": "CERNER"
},
"address": {
"text": "2401 LEMONE INDUSTRIAL BLVD, COLUMBIA MO 65201 ",
"line": "2401 LEMONE INDUSTRIAL BLVD",
"city": "COLUMBIA",
"state": "MO",
"postalCode": "65201"
}
}],
"communication": [{
"language": {
"coding": [{
"system": "urn:oid:2.16.840.1.113883.3.566",
"code": "ENG",
"userSelected": true
}]
}
}],
"generalPractitioner": [{
"reference": "#p1"
}],
"managingOrganization": {
"display": "Organization/UHC"
}
}

19
pom.xml
View File

@ -538,6 +538,11 @@
<artifactId>Saxon-HE</artifactId>
<version>9.5.1-5</version>
</dependency>
<dependency>
<groupId>net.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
@ -1769,6 +1774,20 @@
</plugins>
</build>
</profile>
<profile>
<id>MINPARALLEL</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkCount>2</forkCount>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>ERRORPRONE</id>
<build>

View File

@ -32,6 +32,26 @@
Fix HTTP 500 error in JPA server if a numeric search parameter was supplied with no value, e.g.
<![CDATA[<code>GET /Observation?value-quantity=</code>]]>
</action>
<action type="add">
JPA server transaction processing now honours the Prefer header and includes
created and updated resource bodies in the response bundle if it is set
appropriately.
</action>
<action type="add">
Optimize queries in JPA server remove a few redundant select columns when performing
searches. This provides a slight speed increase in some cases.
</action>
<action type="add">
Add configuration to JPA server DaoConfig that allows a maximum
number of search results to be specified. Queries will never return
more than this number, which can be good for avoiding accidental
performance problems in situations where lare queries should not be
needed
</action>
<action type="fix" issue="674">
Prevent duplicates in $everything query response in JPA server. Thanks to @vlad-ignatov
for reporting!
</action>
</release>
<release version="2.5" date="2017-06-08">
<action type="fix">

View File

@ -145,4 +145,26 @@
</answer>
</faq>
</part>
<part id="Contributing">
<title>Contributing</title>
<faq id="vm_quit_during_build">
<question>
My build is failing with the following error:
<code>[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.19.1:test (default-test) on project hapi-fhir-jpaserver-base: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.19.1:test failed: The forked VM terminated without properly saying goodbye. VM crash or System.exit called?</code>
</question>
<answer>
<p>
This typically means that your build is running out of memory. HAPI's unit tests execute by
default in multiple threads (the thread count is determined by the number of CPU cores available)
so in an environment with lots of cores but not enough RAM, you may run out. If you are getting
this error, try executing the build with the following arguments:
</p>
<pre>mvn -P ALLMODULES,NOPARALLEL install</pre>
<p>
See <a href="/hacking_hapi_fhir.html">Hacking HAPI FHIR</a> for more information on
the build process.
</p>
</answer>
</faq>
</part>
</faqs>