diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Include.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Include.java
index 6fbb2d505f8..a545745c658 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Include.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Include.java
@@ -1,8 +1,9 @@
package ca.uhn.fhir.model.api;
-import org.apache.commons.lang3.builder.ToStringBuilder;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
-import ch.qos.logback.core.db.dialect.MySQLDialect;
+import org.apache.commons.lang3.builder.ToStringBuilder;
/*
* #%L
@@ -34,9 +35,9 @@ import ch.qos.logback.core.db.dialect.MySQLDialect;
*/
public class Include {
+ private final boolean myImmutable;
private boolean myRecurse;
private String myValue;
- private final boolean myImmutable;
/**
* Constructor for non-recursive include
@@ -91,22 +92,6 @@ public class Include {
return new Include(myValue, true);
}
- public String getValue() {
- return myValue;
- }
-
- /**
- * See the note on equality on the {@link Include class documentation}
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + (myRecurse ? 1231 : 1237);
- result = prime * result + ((myValue == null) ? 0 : myValue.hashCode());
- return result;
- }
-
/**
* See the note on equality on the {@link Include class documentation}
*/
@@ -135,6 +120,73 @@ public class Include {
return true;
}
+ /**
+ * Returns the portion of the value before the first colon
+ */
+ public String getParamType() {
+ int firstColon = myValue.indexOf(':');
+ if (firstColon == -1 || firstColon == myValue.length() - 1) {
+ return null;
+ }
+ return myValue.substring(0, firstColon);
+ }
+
+ /**
+ * Returns the portion of the value after the first colon but before the second colon
+ */
+ public String getParamName() {
+ int firstColon = myValue.indexOf(':');
+ if (firstColon == -1 || firstColon == myValue.length() - 1) {
+ return null;
+ }
+ int secondColon = myValue.indexOf(':', firstColon + 1);
+ if (secondColon != -1) {
+ return myValue.substring(firstColon + 1, secondColon);
+ } else {
+ return myValue.substring(firstColon + 1);
+ }
+ }
+
+ /**
+ * Returns the portion of the string after the second colon, or null if there are not two colons in the value.
+ */
+ public String getParamTargetType() {
+ int firstColon = myValue.indexOf(':');
+ if (firstColon == -1 || firstColon == myValue.length() - 1) {
+ return null;
+ }
+ int secondColon = myValue.indexOf(':', firstColon + 1);
+ if (secondColon != -1) {
+ return myValue.substring(secondColon + 1);
+ } else {
+ return null;
+ }
+
+ }
+
+ public String getValue() {
+ return myValue;
+ }
+
+ /**
+ * See the note on equality on the {@link Include class documentation}
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (myRecurse ? 1231 : 1237);
+ result = prime * result + ((myValue == null) ? 0 : myValue.hashCode());
+ return result;
+ }
+
+ /**
+ * Is this object {@link #toLocked() locked}?
+ */
+ public boolean isLocked() {
+ return myImmutable;
+ }
+
public boolean isRecurse() {
return myRecurse;
}
@@ -151,20 +203,13 @@ public class Include {
}
/**
- * Is this object {@link #toLocked() locked}?
- */
- public boolean isLocked() {
- return myImmutable;
- }
-
- /**
- * Return a new
+ * Return a new
*/
public Include toLocked() {
Include retVal = new Include(myValue, myRecurse, true);
return retVal;
}
-
+
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
@@ -172,36 +217,56 @@ public class Include {
builder.append("recurse", myRecurse);
return builder.toString();
}
-
+
/**
- * Creates and returns a new copy of this Include with the given type. The
- * following table shows what will be returned:
+ * Creates and returns a new copy of this Include with the given type. The following table shows what will be
+ * returned:
*
- * Initial Contents | theResourceType | Output |
- * Patient:careProvider | Organization | Patient:careProvider:Organization |
- * Patient:careProvider:Practitioner | Organization | Patient:careProvider:Organization |
- * Patient | (any) | {@link IllegalStateException} |
+ *
+ * Initial Contents |
+ * theResourceType |
+ * Output |
+ *
+ *
+ * Patient:careProvider
+ * | Organization |
+ * Patient:careProvider:Organization |
+ *
+ *
+ * Patient:careProvider:Practitioner
+ * | Organization |
+ * Patient:careProvider:Organization |
+ *
+ *
+ * Patient
+ * | (any) |
+ * {@link IllegalStateException} |
+ *
*
*
- * @param theResourceType The resource type (e.g. "Organization")
- * @return A new copy of the include. Note that if this include is {@link #toLocked() locked}, the returned include will be too
+ * @param theResourceType
+ * The resource type (e.g. "Organization")
+ * @return A new copy of the include. Note that if this include is {@link #toLocked() locked}, the returned include
+ * will be too
*/
public Include withType(String theResourceType) {
StringBuilder b = new StringBuilder();
- b.append(myValue);
- int firstColon = myValue.indexOf(':');
- if (firstColon == -1 || firstColon == b.length() - 1) {
+
+ String paramType = getParamType();
+ String paramName = getParamName();
+ if (isBlank(paramType) || isBlank(paramName)) {
throw new IllegalStateException("This include does not contain a value in the format [ResourceType]:[paramName]");
}
- int secondColon = myValue.indexOf(':', firstColon + 1);
- if (secondColon != -1) {
- b.delete(secondColon, b.length());
- }
-
+ b.append(paramType);
b.append(":");
- b.append(theResourceType);
+ b.append(paramName);
+
+ if (isNotBlank(theResourceType)) {
+ b.append(':');
+ b.append(theResourceType);
+ }
Include retVal = new Include(b.toString(), myRecurse, myImmutable);
return retVal;
}
-
+
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
index 83b375b1507..a49496397e3 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
@@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.dao;
+import static org.apache.commons.lang3.StringUtils.defaultString;
+
/*
* #%L
* HAPI FHIR JPA Server
@@ -1378,19 +1380,18 @@ public class SearchBuilder {
if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
paths = Collections.singletonList(nextInclude.getValue());
} else {
- int colonIdx = nextInclude.getValue().indexOf(':');
- if (colonIdx < 2) {
+ String resType = nextInclude.getParamType();
+ if (isBlank(resType)) {
continue;
}
- String resType = nextInclude.getValue().substring(0, colonIdx);
RuntimeResourceDefinition def = myContext.getResourceDefinition(resType);
if (def == null) {
ourLog.warn("Unknown resource type in include/revinclude=" + nextInclude.getValue());
continue;
}
- String paramName = nextInclude.getValue().substring(colonIdx + 1);
- RuntimeSearchParam param = def.getSearchParam(paramName);
+ String paramName = nextInclude.getParamName();
+ RuntimeSearchParam param = isNotBlank(paramName) ? def.getSearchParam(paramName) : null;
if (param == null) {
ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue());
continue;
@@ -1399,11 +1400,20 @@ public class SearchBuilder {
paths = param.getPathsSplit();
}
+ String targetResourceType = defaultString(nextInclude.getParamTargetType(), null);
for (String nextPath : paths) {
- String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)";
+ String sql;
+ if (targetResourceType != null) {
+ sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids) AND r.myTargetResourceType = :target_resource_type";
+ } else {
+ sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)";
+ }
TypedQuery q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("src_path", nextPath);
q.setParameter("target_pids", nextRoundMatches);
+ if (targetResourceType != null) {
+ q.setParameter("target_resource_type", targetResourceType);
+ }
List results = q.getResultList();
for (ResourceLink resourceLink : results) {
if (theReverseMode) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java
index 8d45916f071..02b1db83eac 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java
@@ -36,6 +36,7 @@ import javax.persistence.Table;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.hibernate.annotations.ColumnDefault;
import org.hibernate.search.annotations.Field;
@Entity
@@ -64,6 +65,11 @@ public class ResourceLink implements Serializable {
@Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable = false)
private Long mySourceResourcePid;
+ @Column(name = "SOURCE_RESOURCE_TYPE", nullable=false, length=ResourceTable.RESTYPE_LEN)
+ @ColumnDefault("''") // TODO: remove this (it's only here for simplifying upgrades of 1.3 -> 1.4)
+ @Field()
+ private String mySourceResourceType;
+
@ManyToOne(optional = false, fetch=FetchType.LAZY)
@JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myTargetResource;
@@ -71,18 +77,21 @@ public class ResourceLink implements Serializable {
@Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false, nullable = false)
@Field()
private Long myTargetResourcePid;
-
+
+ @Column(name = "TARGET_RESOURCE_TYPE", nullable=false, length=ResourceTable.RESTYPE_LEN)
+ @ColumnDefault("''") // TODO: remove this (it's only here for simplifying upgrades of 1.3 -> 1.4)
+ @Field()
+ private String myTargetResourceType;
+
public ResourceLink() {
super();
}
public ResourceLink(String theSourcePath, ResourceTable theSourceResource, ResourceTable theTargetResource) {
super();
- mySourcePath = theSourcePath;
- mySourceResource = theSourceResource;
- mySourceResourcePid = theSourceResource.getId();
- myTargetResource = theTargetResource;
- myTargetResourcePid = theTargetResource.getId();
+ setSourcePath(theSourcePath);
+ setSourceResource(theSourceResource);
+ setTargetResource(theTargetResource);
}
@Override
@@ -140,12 +149,14 @@ public class ResourceLink implements Serializable {
public void setSourceResource(ResourceTable theSourceResource) {
mySourceResource = theSourceResource;
mySourceResourcePid = theSourceResource.getId();
+ mySourceResourceType = theSourceResource.getResourceType();
}
public void setTargetResource(ResourceTable theTargetResource) {
Validate.notNull(theTargetResource);
myTargetResource = theTargetResource;
myTargetResourcePid = theTargetResource.getId();
+ myTargetResourceType = theTargetResource.getResourceType();
}
@Override
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java
index e1010cd45eb..03820122be1 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java
@@ -1462,6 +1462,13 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
patientId2 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
}
+ {
+ // Typed include
+ SearchParameterMap params = new SearchParameterMap();
+ params.addInclude(Patient.INCLUDE_CAREPROVIDER.withType("Practitioner"));
+ List ids = toUnqualifiedVersionlessIds(myPatientDao.search(params));
+ assertThat(ids, containsInAnyOrder(patientId, patientId2, practId2));
+ }
{
// No includes
SearchParameterMap params = new SearchParameterMap();
@@ -1545,13 +1552,6 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
List ids = toUnqualifiedVersionlessIds(myPatientDao.search(params));
assertThat(ids, containsInAnyOrder(orgId, patientId, patientId2, practId2));
}
- {
-// // Typed include
-// SearchParameterMap params = new SearchParameterMap();
-// params.addInclude(Patient.INCLUDE_CAREPROVIDER.withType("Practitioner"));
-// List ids = toUnqualifiedVersionlessIds(myPatientDao.search(params));
-// assertThat(ids, containsInAnyOrder(patientId, patientId2, practId2));
- }
}
@SuppressWarnings("unused")
diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/IncludeTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/IncludeTest.java
index 8bc9e169ed6..ccbe4e00a69 100644
--- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/IncludeTest.java
+++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/IncludeTest.java
@@ -177,6 +177,8 @@ public class IncludeTest {
assertEquals("Patient:careProvider:Practitioner", new Include("Patient:careProvider", true).withType("Practitioner").getValue());
assertEquals(true, new Include("Patient:careProvider", true).withType("Practitioner").isRecurse());
assertEquals(false, new Include("Patient:careProvider:Organization", true).withType("Practitioner").isLocked());
+ assertEquals("Practitioner", new Include("Patient:careProvider", true).withType("Practitioner").getParamTargetType());
+ assertEquals(null, new Include("Patient:careProvider", true).getParamTargetType());
assertEquals("Patient:careProvider:Practitioner", new Include("Patient:careProvider:Organization", true).withType("Practitioner").getValue());
assertEquals(true, new Include("Patient:careProvider:Organization", true).toLocked().withType("Practitioner").isLocked());
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index a467f8728ec..b6e46d48b9a 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -71,6 +71,11 @@
valueString instead of valueMarkdown. After discussion with Grahame, this appears to
be incorrect behaviour so it has been fixed.
+
+ Support target parameter type in _include / _revinclude values, e.g.
+ _include=Patient:careProvider:Organization. Thanks to Joe Portner
+ for reporting!
+