Generic client now allows search by URL

This commit is contained in:
James Agnew 2015-10-05 15:30:30 -04:00
parent 43aad1eb98
commit 80575b5380
32 changed files with 748 additions and 100 deletions

View File

@ -264,6 +264,19 @@ public class GenericClientExample {
.execute();
// END SNIPPET: searchCompartment
// START SNIPPET: searchUrl
String searchUrl = "http://example.com/base/Patient?identifier=foo";
// Search URL can also be a relative URL in which case the client's base
// URL will be added to it
searchUrl = "Patient?identifier=foo";
response = client.search()
.byUrl(searchUrl)
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
// END SNIPPET: searchUrl
// START SNIPPET: searchSubsetSummary
response = client.search()
.forResource(Patient.class)

View File

@ -19,7 +19,8 @@ package ca.uhn.fhir.rest.client;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.Reader;
@ -144,6 +145,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.util.ICallable;
import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.UrlUtil;
/**
* @author James Agnew
@ -724,7 +726,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public ICreateTyped conditionalByUrl(String theSearchUrl) {
mySearchUrl = theSearchUrl;
mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl);
return this;
}
@ -899,8 +901,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public IDeleteTyped resourceConditionalByUrl(String theSearchUrl) {
Validate.notBlank(theSearchUrl, "theSearchUrl can not be blank/null");
mySearchUrl = theSearchUrl;
mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl);
return this;
}
@ -1473,12 +1474,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
Validate.notNull(theParameterType, "theParameterType must not be null");
Validate.notEmpty(theName, "theName must not be null");
Validate.notNull(theValue, "theValue must not be null");
myParametersDef = myContext.getResourceDefinition(theParameterType);
myParameters = (IBaseParameters) myParametersDef.newInstance();
addParam(theName, theValue);
return this;
}
@ -1486,14 +1487,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
private void addParam(String theName, IBase theValue) {
BaseRuntimeChildDefinition parameterChild = myParametersDef.getChildByName("parameter");
BaseRuntimeElementCompositeDefinition<?> parameterElem = (BaseRuntimeElementCompositeDefinition<?>) parameterChild.getChildByName("parameter");
IBase parameter = parameterElem.newInstance();
parameterChild.getMutator().addValue(myParameters, parameter);
IPrimitiveType<String> name = (IPrimitiveType<String>) myContext.getElementDefinition("string").newInstance();
name.setValue(theName);
parameterElem.getChildByName("name").getMutator().setValue(parameter, name);
if (theValue instanceof IBaseDatatype) {
String childElementName = "value" + StringUtils.capitalize(myContext.getElementDefinition(theValue.getClass()).getName());
parameterElem.getChildByName(childElementName).getMutator().setValue(parameter, theValue);
@ -1782,10 +1783,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
private List<TokenParam> mySecurity = new ArrayList<TokenParam>();
private List<SortInternal> mySort = new ArrayList<SortInternal>();
private List<TokenParam> myTags = new ArrayList<TokenParam>();
private String mySearchUrl;
public SearchInternal() {
myResourceType = null;
myResourceName = null;
mySearchUrl = null;
}
@Override
@ -1857,7 +1860,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
IdDt resourceId = myResourceId != null ? new IdDt(myResourceId) : null;
BaseHttpClientInvocation invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle);
BaseHttpClientInvocation invocation;
if (mySearchUrl != null) {
invocation = SearchMethodBinding.createSearchInvocation(mySearchUrl, params);
} else {
invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle);
}
return invoke(params, binding, invocation);
@ -1975,6 +1983,34 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
@Override
public IQuery byUrl(String theSearchUrl) {
Validate.notBlank(theSearchUrl, "theSearchUrl must not be blank/null");
if (theSearchUrl.startsWith("http://") || theSearchUrl.startsWith("https://")) {
mySearchUrl = theSearchUrl;
int qIndex = mySearchUrl.indexOf('?');
if (qIndex != -1) {
mySearchUrl = mySearchUrl.substring(0, qIndex) + validateAndEscapeConditionalUrl(mySearchUrl.substring(qIndex));
}
} else {
String searchUrl = theSearchUrl;
if (searchUrl.startsWith("/")) {
searchUrl = searchUrl.substring(1);
}
if (!searchUrl.matches("[a-zA-Z]+($|\\?.*)")) {
throw new IllegalArgumentException("Search URL must be either a complete URL starting with http: or https:, or a relative FHIR URL in the form [ResourceType]?[Params]");
}
int qIndex = searchUrl.indexOf('?');
if (qIndex == -1) {
mySearchUrl = getUrlBase() + '/' + searchUrl;
} else {
mySearchUrl = getUrlBase() + '/' + validateAndEscapeConditionalUrl(searchUrl);
}
}
return this;
}
}
@SuppressWarnings("rawtypes")
@ -2154,7 +2190,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public IUpdateTyped conditionalByUrl(String theSearchUrl) {
mySearchUrl = theSearchUrl;
mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl);
return this;
}
@ -2290,4 +2326,34 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private static String validateAndEscapeConditionalUrl(String theSearchUrl) {
Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null");
StringBuilder b = new StringBuilder();
boolean haveHadQuestionMark = false;
for (int i = 0; i < theSearchUrl.length(); i++) {
char nextChar = theSearchUrl.charAt(i);
if (!haveHadQuestionMark) {
if (nextChar == '?') {
haveHadQuestionMark = true;
} else if (!Character.isLetter(nextChar)) {
throw new IllegalArgumentException("Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl);
}
b.append(nextChar);
} else {
switch (nextChar) {
case '|':
case '?':
case '$':
case ':':
b.append(UrlUtil.escape(Character.toString(nextChar)));
break;
default:
b.append(nextChar);
break;
}
}
}
return b.toString();
}
}

View File

@ -24,7 +24,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.api.Bundle;
public interface IUntypedQuery {
IQuery<Bundle> forAllResources();
@ -32,5 +31,15 @@ public interface IUntypedQuery {
IQuery<Bundle> forResource(String theResourceName);
IQuery<Bundle> forResource(Class<? extends IBaseResource> theClass);
/**
* Perform a search directly by URL. It is usually better to construct the URL using the {@link #forAllResources()}, {@link #forResource(Class)} etc, but sometimes it is useful to simply search by
* entering a search URL directly.
*
* @param theSearchUrl
* The URL to search for. Note that this URL may be complete (e.g. "http://example.com/base/Patient?name=foo") in which case the client's base URL will be ignored. Or it can be relative
* (e.g. "Patient?name=foo") in which case the client's base URL will be used.
*/
IQuery<Bundle> byUrl(String theSearchUrl);
}

View File

@ -48,6 +48,7 @@ import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.UrlUtil;
/**
* @author James Agnew

View File

@ -477,4 +477,8 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
}
public static BaseHttpClientInvocation createSearchInvocation(String theSearchUrl, Map<String, List<String>> theParams) {
return new HttpGetClientInvocation(theParams, theSearchUrl);
}
}

View File

@ -10,7 +10,6 @@
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<packaging>jar</packaging>

View File

@ -16,6 +16,11 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>

View File

@ -93,11 +93,10 @@
</dependency>
<!-- FHIR RI is pulled in for UCUM support, but we don't want any of its dependencies. -->
<!-- <dependency> <groupId>me.fhir</groupId> <artifactId>fhir-dstu1</artifactId> <version>0.0.81.2489</version> <exclusions> <exclusion> <artifactId>Saxon-HE</artifactId>
<groupId>net.sf.saxon</groupId> </exclusion> <exclusion> <artifactId>commons-discovery</artifactId> <groupId>commons-discovery</groupId> </exclusion> <exclusion>
<artifactId>commons-codec</artifactId> <groupId>commons-codec</groupId> </exclusion> <exclusion> <artifactId>commons-logging</artifactId> <groupId>commons-logging</groupId>
</exclusion> <exclusion> <artifactId>xpp3</artifactId> <groupId>xpp3</groupId> </exclusion> <exclusion> <artifactId>junit</artifactId> <groupId>junit</groupId> </exclusion>
<exclusion> <artifactId>jdom</artifactId> <groupId>org.jdom</groupId> </exclusion> <exclusion> <artifactId>gson</artifactId> <groupId>com.google.code.gson</groupId>
<!-- <dependency> <groupId>me.fhir</groupId> <artifactId>fhir-dstu1</artifactId> <version>0.0.81.2489</version> <exclusions> <exclusion> <artifactId>Saxon-HE</artifactId> <groupId>net.sf.saxon</groupId>
</exclusion> <exclusion> <artifactId>commons-discovery</artifactId> <groupId>commons-discovery</groupId> </exclusion> <exclusion> <artifactId>commons-codec</artifactId> <groupId>commons-codec</groupId>
</exclusion> <exclusion> <artifactId>commons-logging</artifactId> <groupId>commons-logging</groupId> </exclusion> <exclusion> <artifactId>xpp3</artifactId> <groupId>xpp3</groupId> </exclusion> <exclusion>
<artifactId>junit</artifactId> <groupId>junit</groupId> </exclusion> <exclusion> <artifactId>jdom</artifactId> <groupId>org.jdom</groupId> </exclusion> <exclusion> <artifactId>gson</artifactId> <groupId>com.google.code.gson</groupId>
</exclusion> </exclusions> </dependency> -->
@ -230,7 +229,12 @@
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
<version>5.5.0.Final</version>
</dependency>
<!-- Misc -->
<dependency>
<groupId>com.google.guava</groupId>
@ -400,8 +404,8 @@
<target>SCRIPT</target>
<skip>${skip-hib4}</skip>
</configuration>
<!-- This needs to be uncommented in order for this plugin to work with Hibernate 4.3+ (as of hibernate4-maven-plugin version 1.0.5) <dependencies> <dependency>
<groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate_version}</version> </dependency> </dependencies> -->
<!-- This needs to be uncommented in order for this plugin to work with Hibernate 4.3+ (as of hibernate4-maven-plugin version 1.0.5) <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId>
<version>${hibernate_version}</version> </dependency> </dependencies> -->
<executions>
<execution>
<id>o10g</id>

View File

@ -35,6 +35,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
@ -75,6 +76,7 @@ import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.entity.BaseTag;
@ -88,6 +90,8 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.entity.ResourceLink;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.ResourceTag;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchResult;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.util.StopWatch;
@ -157,6 +161,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
private Class<T> myResourceType;
private String mySecondaryPrimaryKeyParamName;
@Autowired
private ISearchResultDao mySearchResultDao;
private Set<Long> addPredicateComposite(RuntimeSearchParam theParamDef, Set<Long> thePids, List<? extends IQueryParameterType> theNextAnd) {
// TODO: fail if missing is set for a composite query
@ -625,9 +632,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
if (params instanceof ReferenceParam) {
ReferenceParam ref = (ReferenceParam) params;
String resourceId = ref.getValueAsQueryToken();
if (isBlank(ref.getChain())) {
String resourceId = ref.getValueAsQueryToken();
if (resourceId.contains("/")) {
IIdType dt = new IdDt(resourceId);
resourceId = dt.getIdPart();
@ -646,13 +652,17 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
throw new ConfigurationException("Property " + paramPath + " of type " + myResourceName + " is not a resource: " + def.getClass());
}
List<Class<? extends IBaseResource>> resourceTypes;
if (isBlank(ref.getResourceType())) {
String resourceId;
if (!ref.getValue().matches("[a-zA-Z]+\\/.*")) {
RuntimeChildResourceDefinition resDef = (RuntimeChildResourceDefinition) def;
resourceTypes = resDef.getResourceTypes();
resourceId = ref.getValue();
} else {
resourceTypes = new ArrayList<Class<? extends IBaseResource>>();
RuntimeResourceDefinition resDef = getContext().getResourceDefinition(ref.getResourceType());
resourceTypes.add(resDef.getImplementingClass());
resourceId = ref.getIdPart();
}
boolean foundChainMatch = false;
@ -1348,11 +1358,20 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public DaoMethodOutcome deleteByUrl(String theUrl) {
return deleteByUrl(theUrl, false);
}
@Override
public DaoMethodOutcome deleteByUrl(String theUrl, boolean theInTransaction) {
StopWatch w = new StopWatch();
Set<Long> resource = processMatchUrl(theUrl, myResourceType);
if (resource.isEmpty()) {
throw new ResourceNotFoundException(getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "unableToDeleteNotFound", theUrl));
if (!theInTransaction) {
throw new ResourceNotFoundException(getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "unableToDeleteNotFound", theUrl));
} else {
return new DaoMethodOutcome();
}
} else if (resource.size() > 1) {
if (myDaoConfig.isAllowMultipleDelete() == false) {
throw new PreconditionFailedException(getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size()));
@ -1640,6 +1659,46 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
}
@Override
public IBundleProvider everything(IIdType theId) {
Search search = new Search();
search.setUuid(UUID.randomUUID().toString());
search.setCreated(new Date());
myEntityManager.persist(search);
List<SearchResult> results = new ArrayList<SearchResult>();
if (theId != null) {
Long pid = translateForcedIdToPid(theId);
ResourceTable entity = myEntityManager.find(ResourceTable.class, pid);
validateGivenIdIsAppropriateToRetrieveResource(theId, entity);
SearchResult res = new SearchResult(search);
res.setResourcePid(pid);
results.add(res);
} else {
TypedQuery<Tuple> query = createSearchAllByTypeQuery();
for (Tuple next : query.getResultList()) {
SearchResult res = new SearchResult(search);
res.setResourcePid(next.get(0, Long.class));
results.add(res);
}
}
mySearchResultDao.save(results);
mySearchResultDao.flush();
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
// cq.
Subquery<Long> subQ = cq.subquery(Long.class);
Root<ResourceLink> subQfrom = subQ.from(ResourceLink.class);
subQ.select(subQfrom.get("mySourceResourceId").as(Long.class));
// subQ.where(builder.in(subQfrom.get("myTargetResourceId"), y));
return null;
}
/**
* THIS SHOULD RETURN HASHSET and not jsut Set because we add to it later (so it can't be Collections.emptySet())
*/
@ -1650,15 +1709,20 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
if (theRevIncludes == null || theRevIncludes.isEmpty()) {
return new HashSet<Long>();
}
String fieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
Collection<Long> nextRoundMatches = theMatches;
HashSet<Long> allAdded = new HashSet<Long>();
HashSet<Long> original = new HashSet<Long>(theMatches);
ArrayList<Include> includes = new ArrayList<Include>(theRevIncludes);
int roundCounts = 0;
StopWatch w = new StopWatch();
boolean addedSomeThisRound;
do {
roundCounts++;
HashSet<Long> pidsToInclude = new HashSet<Long>();
Set<Long> nextRoundOmit = new HashSet<Long>();
@ -1671,13 +1735,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
boolean matchAll = "*".equals(nextInclude.getValue());
if (matchAll) {
String sql;
sql = "SELECT r FROM ResourceLink r WHERE r." + fieldName + " IN (:target_pids)";
sql = "SELECT r FROM ResourceLink r WHERE r." + searchFieldName + " IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("target_pids", nextRoundMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
if (theReverseMode) {
if (theEverythingModeEnum == EverythingModeEnum.ENCOUNTER) {
if (theEverythingModeEnum.isEncounter()) {
if (resourceLink.getSourcePath().equals("Encounter.subject") || resourceLink.getSourcePath().equals("Encounter.patient")) {
nextRoundOmit.add(resourceLink.getSourceResourcePid());
}
@ -1715,7 +1779,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
for (String nextPath : paths) {
String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + fieldName + " IN (:target_pids)";
String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("src_path", nextPath);
q.setParameter("target_pids", nextRoundMatches);
@ -1743,6 +1807,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
nextRoundMatches = pidsToInclude;
} while (includes.size() > 0 && nextRoundMatches.size() > 0 && addedSomeThisRound);
ourLog.info("Loaded {} {} in {} rounds and {} ms", new Object[] {
allAdded.size(), theReverseMode ? "_revincludes" : "_includes", roundCounts, w.getMillisAndRestart()
});
return allAdded;
}
@ -2038,15 +2106,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
Set<Long> loadPids;
if (theParams.isEmpty()) {
loadPids = new HashSet<Long>();
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.multiselect(from.get("myId").as(Long.class));
Predicate typeEquals = builder.equal(from.get("myResourceType"), myResourceName);
Predicate notDeleted = builder.isNull(from.get("myDeleted"));
cq.where(builder.and(typeEquals, notDeleted));
TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
TypedQuery<Tuple> query = createSearchAllByTypeQuery();
for (Tuple next : query.getResultList()) {
loadPids.add(next.get(0, Long.class));
}
@ -2163,6 +2223,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return retVal;
}
private TypedQuery<Tuple> createSearchAllByTypeQuery() {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.multiselect(from.get("myId").as(Long.class));
Predicate typeEquals = builder.equal(from.get("myResourceType"), myResourceName);
Predicate notDeleted = builder.isNull(from.get("myDeleted"));
cq.where(builder.and(typeEquals, notDeleted));
TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
return query;
}
private List<Long> processSort(final SearchParameterMap theParams, Set<Long> theLoadPids) {
final List<Long> pids;
Set<Long> loadPids = theLoadPids;

View File

@ -45,7 +45,7 @@ public class FhirResourceDaoEncounterDstu2 extends FhirResourceDaoDstu2<Encounte
paramMap.setRevIncludes(Collections.singleton(IResource.INCLUDE_ALL.asRecursive()));
paramMap.setIncludes(Collections.singleton(IResource.INCLUDE_ALL.asRecursive()));
paramMap.setEverythingMode(EverythingModeEnum.ENCOUNTER);
paramMap.setEverythingMode(theId != null ? EverythingModeEnum.ENCOUNTER_INSTANCE : EverythingModeEnum.ENCOUNTER_TYPE);
paramMap.setSort(theSort);
paramMap.setLastUpdated(theLastUpdated);
if (theId != null) {

View File

@ -45,7 +45,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2<Patient>im
paramMap.setRevIncludes(Collections.singleton(IResource.INCLUDE_ALL.asRecursive()));
paramMap.setIncludes(Collections.singleton(IResource.INCLUDE_ALL.asRecursive()));
paramMap.setEverythingMode(EverythingModeEnum.PATIENT);
paramMap.setEverythingMode(theId != null ? EverythingModeEnum.PATIENT_INSTANCE : EverythingModeEnum.PATIENT_TYPE);
paramMap.setSort(theSort);
paramMap.setLastUpdated(theLastUpdated);
if (theId != null) {

View File

@ -38,6 +38,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@ -92,7 +93,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public void pollForNewUndeliveredResources() {
public synchronized void pollForNewUndeliveredResources() {
if (getConfig().isSubscriptionEnabled() == false) {
return;
}
@ -106,7 +107,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
List<SubscriptionTable> subscriptions = q.getResultList();
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
for (final SubscriptionTable nextSubscriptionTable : subscriptions) {
txTemplate.execute(new TransactionCallback<Void>() {
@Override
@ -253,7 +254,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
}
@Override
public List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
public synchronized List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
Page<SubscriptionFlaggedResource> flaggedResources = mySubscriptionFlaggedResourceDataDao.findAllBySubscriptionId(theSubscriptionPid, new PageRequest(0, 100));
for (SubscriptionFlaggedResource nextFlaggedResource : flaggedResources) {
@ -295,7 +296,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}",
new Object[] { subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll() });
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {

View File

@ -282,7 +282,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
if (parts.getResourceId() != null) {
dao.delete(new IdDt(parts.getResourceType(), parts.getResourceId()));
} else {
dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams());
dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams(), true);
}
newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_204_NO_CONTENT));

View File

@ -139,4 +139,14 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
*/
MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile);
/**
* @param theTransaction Is this being called in a bundle? If so, don't throw an exception if no matches
*/
DaoMethodOutcome deleteByUrl(String theUrl, boolean theTransaction);
/**
* Invoke the everything operation
*/
IBundleProvider everything(IIdType theId);
}

View File

@ -165,7 +165,36 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
}
public enum EverythingModeEnum {
PATIENT, ENCOUNTER
//@formatter:off
PATIENT_TYPE(true, false, false),
PATIENT_INSTANCE(true, false, true),
ENCOUNTER_TYPE(false, true, false),
ENCOUNTER_INSTANCE(false, true, true);
//@formatter:on
private final boolean myPatient;
public boolean isPatient() {
return myPatient;
}
public boolean isEncounter() {
return myEncounter;
}
public boolean isInstance() {
return myInstance;
}
private final boolean myEncounter;
private final boolean myInstance;
private EverythingModeEnum(boolean thePatient, boolean theEncounter, boolean theInstance) {
assert thePatient ^ theEncounter;
myPatient = thePatient;
myEncounter = theEncounter;
myInstance = theInstance;
}
}
}

View File

@ -0,0 +1,9 @@
package ca.uhn.fhir.jpa.dao.data;
import org.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.entity.SearchResult;
public interface ISearchResultDao extends JpaRepository<SearchResult, Long> {
// nothing
}

View File

@ -24,9 +24,11 @@ import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@ -35,9 +37,14 @@ import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import com.phloc.commons.annotations.ContainsSoftMigration;
@Entity
@Table(name = "HFJ_RES_LINK"/* , indexes= {@Index(name="IDX_RL_TPATHRES", columnList= "SRC_PATH,TARGET_RESOURCE_ID")} */)
@org.hibernate.annotations.Table(appliesTo = "HFJ_RES_LINK", indexes = { @org.hibernate.annotations.Index(name = "IDX_RL_TPATHRES", columnNames = { "SRC_PATH", "TARGET_RESOURCE_ID" }) })
@Table(name = "HFJ_RES_LINK" , indexes= {
@Index(name="IDX_RL_TPATHRES", columnList= "SRC_PATH,TARGET_RESOURCE_ID"),
@Index(name="IDX_RL_SRC", columnList= "SRC_RESOURCE_ID"),
@Index(name="IDX_RL_DEST", columnList= "TARGET_RESOURCE_ID")
})
public class ResourceLink implements Serializable {
private static final long serialVersionUID = 1L;
@ -50,14 +57,14 @@ public class ResourceLink implements Serializable {
@Column(name = "SRC_PATH", length = 100, nullable = false)
private String mySourcePath;
@ManyToOne(optional = false)
@ManyToOne(optional = false, fetch=FetchType.LAZY)
@JoinColumn(name = "SRC_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable mySourceResource;
@Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable = false)
private Long mySourceResourcePid;
@ManyToOne(optional = false)
@ManyToOne(optional = false, fetch=FetchType.LAZY)
@JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myTargetResource;
@ -141,8 +148,8 @@ public class ResourceLink implements Serializable {
StringBuilder b = new StringBuilder();
b.append("ResourceLink[");
b.append("path=").append(mySourcePath);
b.append(", src=").append(mySourceResource.getId());
b.append(", target=").append(myTargetResource.getId());
b.append(", src=").append(mySourceResourcePid);
b.append(", target=").append(myTargetResourcePid);
b.append("]");
return b.toString();

View File

@ -0,0 +1,76 @@
package ca.uhn.fhir.jpa.entity;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 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.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
//@formatter:off
@Entity
@Table(name = "HFJ_SEARCH", uniqueConstraints= {
@UniqueConstraint(name="IDX_SEARCH_UUID", columnNames="SEARCH_UUID")
})
//@formatter:on
public class Search implements Serializable {
private static final long serialVersionUID = 1L;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="CREATED", nullable=false)
private Date myCreated;
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SEARCH")
@SequenceGenerator(name="SEQ_SEARCH", sequenceName="SEQ_SEARCH")
@Id
@Column(name = "PID")
private Long myId;
@Column(name="SEARCH_UUID", length=40, nullable=false)
private String myUuid;
public Date getCreated() {
return myCreated;
}
public String getUuid() {
return myUuid;
}
public void setCreated(Date theCreated) {
myCreated = theCreated;
}
public void setUuid(String theUuid) {
myUuid = theUuid;
}
}

View File

@ -0,0 +1,79 @@
package ca.uhn.fhir.jpa.entity;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 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.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
//@formatter:off
@Entity
@Table(name = "HFJ_SEARCH_RESULT", uniqueConstraints= {
})
//@formatter:on
public class SearchResult implements Serializable {
private static final long serialVersionUID = 1L;
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SEARCH_RES")
@SequenceGenerator(name="SEQ_SEARCH_RES", sequenceName="SEQ_SEARCH_RES")
@Id
@Column(name = "PID")
private Long myId;
@ManyToOne
@JoinColumn(name="RESOURCE_PID", referencedColumnName="RES_ID", foreignKey=@ForeignKey(name="FK_SEARCHRES_RES"), insertable=false, updatable=false, nullable=false)
private ResourceTable myResource;
@Column(name="RESOURCE_PID", insertable=true, updatable=false, nullable=false)
private Long myResourcePid;
@ManyToOne
@JoinColumn(name="SEARCH_PID", referencedColumnName="PID", foreignKey=@ForeignKey(name="FK_SEARCHRES_SEARCH"))
private Search mySearch;
/**
* Constructor
*/
public SearchResult() {
// nothing
}
/**
* Constructor
*/
public SearchResult(Search theSearch) {
mySearch = theSearch;
}
public void setResourcePid(Long theResourcePid) {
myResourcePid = theResourcePid;
}
}

View File

@ -120,7 +120,7 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
protected IFhirResourceDao<Organization> myOrganizationDao;
@Autowired
@Qualifier("myPatientDaoDstu2")
protected IFhirResourceDao<Patient> myPatientDao;
protected IFhirResourceDaoPatient<Patient> myPatientDao;
@Autowired
@Qualifier("myPractitionerDaoDstu2")
protected IFhirResourceDao<Practitioner> myPractitionerDao;

View File

@ -11,6 +11,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import java.math.BigDecimal;
import java.util.ArrayList;
@ -20,6 +21,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -39,6 +42,7 @@ import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.dstu.resource.BaseResource;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.composite.IdentifierDt;
@ -52,8 +56,11 @@ import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu2.resource.Encounter;
import ca.uhn.fhir.model.dstu2.resource.Immunization;
import ca.uhn.fhir.model.dstu2.resource.Location;
import ca.uhn.fhir.model.dstu2.resource.Medication;
import ca.uhn.fhir.model.dstu2.resource.MedicationOrder;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.resource.Practitioner;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
@ -570,21 +577,21 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
}
{
Map<String, IQueryParameterType> params = new HashMap<String, IQueryParameterType>();
params.put(Patient.SP_RES_LANGUAGE, new StringParam("en_CA"));
params.put(BaseResource.SP_RES_LANGUAGE, new StringParam("en_CA"));
List<IResource> patients = toList(myPatientDao.search(params));
assertEquals(1, patients.size());
assertEquals(id1.toUnqualifiedVersionless(), patients.get(0).getId().toUnqualifiedVersionless());
}
{
Map<String, IQueryParameterType> params = new HashMap<String, IQueryParameterType>();
params.put(Patient.SP_RES_LANGUAGE, new StringParam("en_US"));
params.put(BaseResource.SP_RES_LANGUAGE, new StringParam("en_US"));
List<Patient> patients = toList(myPatientDao.search(params));
assertEquals(1, patients.size());
assertEquals(id2.toUnqualifiedVersionless(), patients.get(0).getId().toUnqualifiedVersionless());
}
{
Map<String, IQueryParameterType> params = new HashMap<String, IQueryParameterType>();
params.put(Patient.SP_RES_LANGUAGE, new StringParam("en_GB"));
params.put(BaseResource.SP_RES_LANGUAGE, new StringParam("en_GB"));
List<Patient> patients = toList(myPatientDao.search(params));
assertEquals(0, patients.size());
}
@ -610,12 +617,12 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
}
{
SearchParameterMap params = new SearchParameterMap();
params.add(Patient.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US")));
params.add(BaseResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US")));
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1, id2));
}
{
SearchParameterMap params = new SearchParameterMap();
params.add(Patient.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
params.add(BaseResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
}
{
@ -623,7 +630,7 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
StringAndListParam and = new StringAndListParam();
and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")));
params.add(Patient.SP_RES_LANGUAGE, and);
params.add(BaseResource.SP_RES_LANGUAGE, and);
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
}
{
@ -631,7 +638,7 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
StringAndListParam and = new StringAndListParam();
and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
and.addAnd(new StringOrListParam().addOr(new StringParam("ZZZZZ")));
params.add(Patient.SP_RES_LANGUAGE, and);
params.add(BaseResource.SP_RES_LANGUAGE, and);
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty());
}
{
@ -639,7 +646,7 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
StringAndListParam and = new StringAndListParam();
and.addAnd(new StringOrListParam().addOr(new StringParam("ZZZZZ")));
and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
params.add(Patient.SP_RES_LANGUAGE, and);
params.add(BaseResource.SP_RES_LANGUAGE, and);
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty());
}
{
@ -647,7 +654,7 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
StringAndListParam and = new StringAndListParam();
and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null)));
params.add(Patient.SP_RES_LANGUAGE, and);
params.add(BaseResource.SP_RES_LANGUAGE, and);
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
}
{
@ -656,7 +663,7 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
StringAndListParam and = new StringAndListParam();
and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null)));
params.add(Patient.SP_RES_LANGUAGE, and);
params.add(BaseResource.SP_RES_LANGUAGE, and);
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
}
{
@ -664,12 +671,41 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
StringAndListParam and = new StringAndListParam();
and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null)));
params.add(Patient.SP_RES_LANGUAGE, and);
params.add(BaseResource.SP_RES_LANGUAGE, and);
params.add("_id", new StringParam(id1.getIdPart()));
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
}
}
@Test
public void testEverythingTimings() throws Exception {
String methodName = "testEverythingIncludesBackReferences";
Organization org = new Organization();
org.setName(methodName);
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
Medication med = new Medication();
med.getCode().setText(methodName);
IIdType medId = myMedicationDao.create(med).getId().toUnqualifiedVersionless();
Patient pat = new Patient();
pat.addAddress().addLine(methodName);
pat.getManagingOrganization().setReference(orgId);
IIdType patId = myPatientDao.create(pat).getId().toUnqualifiedVersionless();
MedicationOrder mo = new MedicationOrder();
mo.getPatient().setReference(patId);
mo.setMedication(new ResourceReferenceDt(medId));
IIdType moId = myMedicationOrderDao.create(mo).getId().toUnqualifiedVersionless();
HttpServletRequest request = mock(HttpServletRequest.class);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null);
assertEquals(4, resp.size());
}
@Test
public void testSearchLastUpdatedParamWithComparator() throws InterruptedException {
@ -1053,7 +1089,13 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", new Object[] { patientId01, locId01, obsId01, obsId02 });
List<Observation> result = toList(myObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypes01")));
List<Observation> result;
result = toList(myObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam("Patient", Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypes01")));
assertEquals(1, result.size());
assertEquals(obsId01.getIdPart(), result.get(0).getId().getIdPart());
result = toList(myObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypes01")));
assertEquals(2, result.size());
result = toList(myObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypesXX")));
@ -1062,10 +1104,6 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
result = toList(myObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypesYY")));
assertEquals(0, result.size());
result = toList(myObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam("Patient", Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypes01")));
assertEquals(1, result.size());
assertEquals(obsId01.getIdPart(), result.get(0).getId().getIdPart());
}
@Test
@ -1184,12 +1222,12 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
patient.addName().addFamily(value);
longId = (IdDt) myPatientDao.create(patient).getId().toUnqualifiedVersionless();
longId = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
}
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("002");
shortId = (IdDt) myPatientDao.create(patient).getId().toUnqualifiedVersionless();
shortId = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
}
Map<String, IQueryParameterType> params = new HashMap<String, IQueryParameterType>();
@ -1479,7 +1517,7 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
{
SearchParameterMap params = new SearchParameterMap();
params.add(Organization.SP_RES_ID, new StringDt(orgId.getIdPart()));
params.add(ca.uhn.fhir.model.dstu2.resource.BaseResource.SP_RES_ID, new StringDt(orgId.getIdPart()));
params.addInclude(Organization.INCLUDE_PARTOF.asNonRecursive());
List<IIdType> resources = toUnqualifiedVersionlessIds(myOrganizationDao.search(params));
assertThat(resources, contains(orgId, parentOrgId));
@ -1833,8 +1871,6 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
@Test
public void testSearchWithUriParam() throws Exception {
String methodName = "testSearchWithUriParam";
Class<ValueSet> type = ValueSet.class;
String resourceName = "/valueset-dstu2.json";
ValueSet vs = loadResourceFromClasspath(type, resourceName);

View File

@ -822,6 +822,34 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertGone(id);
}
@Test
public void testDeleteWithMatchUrlChainedIdentifier() {
String methodName = "testDeleteWithMatchUrlChainedIdentifer";
Organization org = new Organization();
org.setName(methodName);
org.addIdentifier().setSystem("http://example.com").setValue(methodName);
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
p.getManagingOrganization().setReference(orgId);
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
ourLog.info("Created patient, got it: {}", id);
myPatientDao.deleteByUrl("Patient?organization.identifier=http://example.com|" + methodName);
assertGone(id);
assertNotGone(orgId);
myOrganizationDao.deleteByUrl("Organization?identifier=http://example.com|" + methodName);
assertGone(id);
assertGone(orgId);
}
@Test
public void testDeleteWithMatchUrlChainedTag() {

View File

@ -77,7 +77,8 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
public void testTransactionFromBundle6() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle3.xml");
String bundle = IOUtils.toString(bundleRes);
mySystemDao.transaction(myFhirCtx.newXmlParser().parseResource(Bundle.class, bundle));
Bundle output = mySystemDao.transaction(myFhirCtx.newXmlParser().parseResource(Bundle.class, bundle));
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
}
@Test
@ -712,12 +713,12 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
Bundle request = new Bundle();
request.addEntry().getRequest().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName);
try {
// try {
mySystemDao.transaction(request);
fail();
} catch (ResourceNotFoundException e) {
assertThat(e.getMessage(), containsString("resource matching URL \"Patient?"));
}
// fail();
// } catch (ResourceNotFoundException e) {
// assertThat(e.getMessage(), containsString("resource matching URL \"Patient?"));
// }
}
@Test
@ -797,15 +798,6 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
assertEquals("201 Created", resp.getEntry().get(1).getResponse().getStatus());
}
public static void main(String[] args) {
Communication com = new Communication();
com.getSender().setReference("Patient/james");
com.addRecipient().setReference("Group/everyone");
com.addMedium().setText("Skype");
com.addPayload().setContent(new StringDt("Welcome to Connectathon 10! Any HAPI users feel free to grab me if you want to chat or need help!"));
System.out.println(FhirContext.forDstu2().newJsonParser().setPrettyPrint(true).encodeResourceToString(com));
}
@Test
public void testTransactionWithReferenceToCreateIfNoneExist() {
Bundle bundle = new Bundle();

View File

@ -41,6 +41,7 @@ import org.apache.http.entity.StringEntity;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.Test;
import org.thymeleaf.util.UrlUtils;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
@ -94,6 +95,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.UrlUtil;
public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
@ -269,7 +271,92 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
assertThat(e.getMessage(), containsString("Question with linkId[link0]"));
}
}
@Test
public void testUpdateResourceConditionalComplex() throws IOException {
Patient pt = new Patient();
pt.addIdentifier().setSystem("http://general-hospital.co.uk/Identifiers").setValue("09832345234543876876");
String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt);
HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?identifier=http://general-hospital.co.uk/Identifiers|09832345234543876876");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
IdDt id;
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
assertEquals(201, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id = new IdDt(newIdString);
} finally {
response.close();
}
pt.addName().addFamily("FOO");
resource = myFhirCtx.newXmlParser().encodeResourceToString(pt);
HttpPut put = new HttpPut(ourServerBase + "/Patient?identifier=" + ("http://general-hospital.co.uk/Identifiers|09832345234543876876".replace("|", UrlUtil.escape("|"))));
put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
IdDt id2;
response = ourHttpClient.execute(put);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id2 = new IdDt(newIdString);
} finally {
response.close();
}
assertEquals(id.getIdPart(), id2.getIdPart());
assertEquals("1", id.getVersionIdPart());
assertEquals("2", id2.getVersionIdPart());
}
@Test
public void testCreateResourceConditionalComplex() throws IOException {
Patient pt = new Patient();
pt.addIdentifier().setSystem("http://general-hospital.co.uk/Identifiers").setValue("09832345234543876876");
String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt);
HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?identifier=http://general-hospital.co.uk/Identifiers|09832345234543876876");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
IdDt id;
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
assertEquals(201, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id = new IdDt(newIdString);
} finally {
response.close();
}
IdDt id2;
response = ourHttpClient.execute(post);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id2 = new IdDt(newIdString);
} finally {
response.close();
}
// //@formatter:off
// IIdType id3 = ourClient
// .update()
// .resource(pt)
// .conditionalByUrl("Patient?identifier=http://general-hospital.co.uk/Identifiers|09832345234543876876")
// .execute().getId();
// //@formatter:on
assertEquals(id.getValue(), id2.getValue());
}
@Test
public void testCreateResourceConditional() throws IOException {
String methodName = "testCreateResourceConditional";
@ -2042,7 +2129,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
try {
String resp = IOUtils.toString(response.getEntity().getContent());
ourLog.info(resp);
assertEquals(412, response.getStatusLine().getStatusCode());
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
@ -2069,7 +2156,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
try {
String resp = IOUtils.toString(response.getEntity().getContent());
ourLog.info(resp);
assertEquals(412, response.getStatusLine().getStatusCode());
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();

View File

@ -21,6 +21,8 @@
<class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords</class>
<class>ca.uhn.fhir.jpa.entity.ResourceLink</class>
<class>ca.uhn.fhir.jpa.entity.ResourceTag</class>
<class>ca.uhn.fhir.jpa.entity.Search</class>
<class>ca.uhn.fhir.jpa.entity.SearchResult</class>
<class>ca.uhn.fhir.jpa.entity.SubscriptionTable</class>
<class>ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource</class>
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>

View File

@ -34,7 +34,7 @@
</logger>
<!-- Set to 'trace' to enable SQL logging -->
<logger name="org.hibernate.SQL" additivity="false" level="info">
<logger name="org.hibernate.SQL" additivity="false" level="trace">
<appender-ref ref="STDOUT" />
</logger>

View File

@ -170,7 +170,7 @@
<webApp>
<contextPath>/hapi-fhir-jpaserver-example</contextPath>
<allowDuplicateFragmentNames>true</allowDuplicateFragmentNames>
</webAppConfig>
</webApp>
</configuration>
</plugin>
</plugins>

View File

@ -2,14 +2,18 @@
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
<level>TRACE</level>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<root>
<logger name="org.hibernate.SQL" additivity="false" level="trace">
<appender-ref ref="STDOUT" />
</logger>
<root level="info">
<appender-ref ref="STDOUT" />
</root>

View File

@ -343,6 +343,15 @@ public class GenericClientDstu2Test {
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
client.create().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute();
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, idx), containsString("<family value=\"FOOFAMILY\"/>"));
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).getURI().toString());
assertEquals("http://example.com/fhir/Patient?name=http://foo%7Cbar", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_IF_NONE_EXIST).getValue());
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).execute();
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
@ -1683,6 +1692,84 @@ public class GenericClientDstu2Test {
}
@Test
public void testSearchByUrl() throws Exception {
final String msg = getPatientFeedWithOneResult();
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_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(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;
//@formatter:off
ca.uhn.fhir.model.dstu2.resource.Bundle response = client.search()
.byUrl("http://foo?name=http://foo|bar")
.encodedJson()
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://foo?name=http%3A//foo%7Cbar&_format=json", capt.getAllValues().get(idx).getURI().toString());
assertNotNull(response);
idx++;
//@formatter:off
response = client.search()
.byUrl("Patient?name=http://foo|bar")
.encodedJson()
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?name=http%3A//foo%7Cbar&_format=json", capt.getAllValues().get(idx).getURI().toString());
assertNotNull(response);
idx++;
//@formatter:off
response = client.search()
.byUrl("/Patient?name=http://foo|bar")
.encodedJson()
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?name=http%3A//foo%7Cbar&_format=json", capt.getAllValues().get(idx).getURI().toString());
assertNotNull(response);
idx++;
//@formatter:off
response = client.search()
.byUrl("Patient")
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).getURI().toString());
assertNotNull(response);
idx++;
//@formatter:off
response = client.search()
.byUrl("Patient?")
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?", capt.getAllValues().get(idx).getURI().toString());
assertNotNull(response);
idx++;
try {
client.search().byUrl("foo/bar?test=1");
} catch (IllegalArgumentException e) {
assertEquals("Search URL must be either a complete URL starting with http: or https:, or a relative FHIR URL in the form [ResourceType]?[Params]", e.getMessage());
}
}
@Test
public void testSearchWithSummaryParam() throws Exception {
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\"}]}}]}";
@ -1885,6 +1972,14 @@ public class GenericClientDstu2Test {
assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getURI().toString());
idx++;
client.update().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute();
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, idx), containsString("<family value=\"FOOFAMILY\"/>"));
assertEquals("PUT", capt.getAllValues().get(idx).getRequestLine().getMethod());
assertEquals("http://example.com/fhir/Patient?name=http%3A//foo%7Cbar", capt.getAllValues().get(idx).getURI().toString());
idx++;
client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").execute();
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
@ -1911,7 +2006,6 @@ public class GenericClientDstu2Test {
}
@SuppressWarnings("deprecation")
@Test
public void testUpdateNonFluent() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);

View File

@ -21,6 +21,8 @@
<class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords</class>
<class>ca.uhn.fhir.jpa.entity.ResourceLink</class>
<class>ca.uhn.fhir.jpa.entity.ResourceTag</class>
<class>ca.uhn.fhir.jpa.entity.Search</class>
<class>ca.uhn.fhir.jpa.entity.SearchResult</class>
<class>ca.uhn.fhir.jpa.entity.SubscriptionTable</class>
<class>ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource</class>
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>

View File

@ -144,7 +144,12 @@
process if the condition had a chain or a
qualifier, e.g. "Patient?organization.name" or
"Patient.identifier:missing"
</action>
<action type="add">
Generic/fluent client search can now be
performed using a complete URL supplied
by user code. Thanks to Simone Heckmann
pointing out that this was needed!
</action>
</release>
<release version="1.2" date="2015-09-18">

View File

@ -175,6 +175,19 @@
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<h4>Search - By plain URL</h4>
<p>
You can also perform a search using a String URL, instead
of using the fluent method calls to build the URL. This
can be useful if you have a URL you retrieved from
somewhere else that you want to use as a search.
</p>
<macro name="snippet">
<param name="id" value="searchUrl" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<h4>Search - Other Query Options</h4>
<p>
The fluent search also has methods for sorting, limiting, specifying