Fix #363 - Allow remote references in JPA resources
This commit is contained in:
parent
b03bea3a58
commit
3d8776f6ed
|
@ -48,7 +48,8 @@ ca.uhn.fhir.validation.ValidationResult.noIssuesDetected=No issues detected duri
|
|||
|
||||
# JPA Messages
|
||||
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request.
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlInvalidResourceType=Invalid match URL "{0}" - Unknown resource type: "{1}"
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlNoMatches=Invalid match URL "{0}" - No resources match this search
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlMultipleMatches=Invalid match URL "{0}" - Multiple resources match this search
|
||||
|
|
|
@ -243,14 +243,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Set<ResourceLink> extractResourceLinks(ResourceTable theEntity, IBaseResource theResource) {
|
||||
Set<ResourceLink> retVal = new HashSet<ResourceLink>();
|
||||
protected void extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set<ResourceLink> theLinks) {
|
||||
|
||||
/*
|
||||
* For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
|
||||
*/
|
||||
if (theResource instanceof IBaseBundle) {
|
||||
return retVal;
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
|
||||
|
@ -274,7 +273,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
for (PathAndRef nextPathAndRef : refs) {
|
||||
Object nextObject = nextPathAndRef.getRef();
|
||||
|
||||
ResourceLink nextEntity;
|
||||
IIdType nextId;
|
||||
if (nextObject instanceof IBaseReference) {
|
||||
IBaseReference nextValue = (IBaseReference) nextObject;
|
||||
|
@ -304,6 +302,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
}
|
||||
}
|
||||
|
||||
String baseUrl = nextId.getBaseUrl();
|
||||
String typeString = nextId.getResourceType();
|
||||
if (isBlank(typeString)) {
|
||||
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource type - " + nextId.getValue());
|
||||
|
@ -316,6 +315,18 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
"Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextId.getValue());
|
||||
}
|
||||
|
||||
if (isNotBlank(baseUrl)) {
|
||||
if (!getConfig().getTreatBaseUrlsAsLocal().contains(baseUrl) && !getConfig().isAllowExternalReferences()) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "externalReferenceNotAllowed", nextId.getValue());
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
if (theLinks.add(new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId))) {
|
||||
ourLog.info("Indexing remote resource reference URL: {}", nextId);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
||||
String id = nextId.getIdPart();
|
||||
if (StringUtils.isBlank(id)) {
|
||||
|
@ -357,45 +368,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
continue;
|
||||
}
|
||||
|
||||
// /*
|
||||
// * Is the target type an allowable type of resource for the path where it is referenced?
|
||||
// */
|
||||
//
|
||||
// if (allowedTypesInField == null) {
|
||||
// BaseRuntimeChildDefinition childDef = getContext().newTerser().getDefinition(theResource.getClass(), nextPathAndRef.getPath());
|
||||
// if (childDef instanceof RuntimeChildResourceDefinition) {
|
||||
// RuntimeChildResourceDefinition resRefDef = (RuntimeChildResourceDefinition) childDef;
|
||||
// allowedTypesInField = resRefDef.getResourceTypes();
|
||||
// } else {
|
||||
// allowedTypesInField = new ArrayList<Class<? extends IBaseResource>>();
|
||||
// allowedTypesInField.add(IBaseResource.class);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// boolean acceptableLink = false;
|
||||
// for (Class<? extends IBaseResource> next : allowedTypesInField) {
|
||||
// if (next.isAssignableFrom(targetResourceDef.getImplementingClass())) {
|
||||
// acceptableLink = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (!acceptableLink) {
|
||||
// throw new UnprocessableEntityException(
|
||||
// "Invalid reference found at path '" + nextPathAndRef.getPath() + "'. Resource type '" + targetResourceDef.getName() + "' is not valid for this path");
|
||||
// }
|
||||
|
||||
nextEntity = new ResourceLink(nextPathAndRef.getPath(), theEntity, target);
|
||||
if (nextEntity != null) {
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
theLinks.add(new ResourceLink(nextPathAndRef.getPath(), theEntity, target));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
theEntity.setHasLinks(retVal.size() > 0);
|
||||
theEntity.setHasLinks(theLinks.size() > 0);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
|
||||
|
@ -1268,7 +1247,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
}
|
||||
}
|
||||
|
||||
links = extractResourceLinks(theEntity, theResource);
|
||||
links = new HashSet<ResourceLink>();
|
||||
extractResourceLinks(theEntity, theResource, links);
|
||||
|
||||
/*
|
||||
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
|
||||
|
|
|
@ -88,6 +88,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
|||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import ca.uhn.fhir.util.ObjectUtil;
|
||||
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends BaseHapiFhirDao<T> implements IFhirResourceDao<T> {
|
||||
|
@ -708,6 +709,24 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithInvalidId", theResource.getIdElement().getIdPart()));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Replace absolute references with relative ones if configured to
|
||||
* do so
|
||||
*/
|
||||
if (getConfig().getTreatBaseUrlsAsLocal().isEmpty() == false) {
|
||||
FhirTerser t = getContext().newTerser();
|
||||
List<ResourceReferenceInfo> refs = t.getAllResourceReferences(theResource);
|
||||
for (ResourceReferenceInfo nextRef : refs) {
|
||||
IIdType refId = nextRef.getResourceReference().getReferenceElement();
|
||||
if (refId != null && refId.hasBaseUrl()) {
|
||||
if (getConfig().getTreatBaseUrlsAsLocal().contains(refId.getBaseUrl())) {
|
||||
IIdType newRefId = refId.toUnqualified();
|
||||
nextRef.getResourceReference().setReference(newRefId.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,8 +3,11 @@ package ca.uhn.fhir.jpa.dao;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
|
||||
|
@ -34,19 +37,24 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
|||
|
||||
public class DaoConfig {
|
||||
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
private boolean myAllowExternalReferences = false;
|
||||
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
private boolean myAllowInlineMatchUrlReferences = false;
|
||||
|
||||
private boolean myAllowMultipleDelete;
|
||||
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
private long myExpireSearchResultsAfterMillis = DateUtils.MILLIS_PER_HOUR;
|
||||
private int myHardSearchLimit = 1000;
|
||||
private int myHardTagListLimit = 1000;
|
||||
|
||||
private int myIncludeLimit = 2000;
|
||||
|
||||
// ***
|
||||
|
@ -55,39 +63,49 @@ public class DaoConfig {
|
|||
private boolean myIndexContainedResources = true;
|
||||
|
||||
private List<IServerInterceptor> myInterceptors;
|
||||
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
private int myMaximumExpansionSize = 5000;
|
||||
|
||||
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
|
||||
|
||||
private boolean mySchedulingDisabled;
|
||||
|
||||
private boolean mySubscriptionEnabled;
|
||||
|
||||
private long mySubscriptionPollDelay = 1000;
|
||||
|
||||
private Long mySubscriptionPurgeInactiveAfterMillis;
|
||||
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>();
|
||||
|
||||
/**
|
||||
* Search results are stored in the database so that they can be paged through. After this
|
||||
* number of milliseconds, they will be deleted from the database. Defaults to 1 hour.
|
||||
* Sets the number of milliseconds that search results for a given client search
|
||||
* should be preserved before being purged from the database.
|
||||
* <p>
|
||||
* Search results are stored in the database so that they can be paged over multiple
|
||||
* requests. After this
|
||||
* number of milliseconds, they will be deleted from the database, and any paging links
|
||||
* (next/prev links in search response bundles) will become invalid. Defaults to 1 hour.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public long getExpireSearchResultsAfterMillis() {
|
||||
return myExpireSearchResultsAfterMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #setIncludeLimit(int)}
|
||||
* Gets the maximum number of results to return in a GetTags query (DSTU1 only)
|
||||
*/
|
||||
public int getHardSearchLimit() {
|
||||
return myHardSearchLimit;
|
||||
}
|
||||
public int getHardTagListLimit() {
|
||||
return myHardTagListLimit;
|
||||
}
|
||||
|
||||
public int getIncludeLimit() {
|
||||
return myIncludeLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the interceptors which will be notified of operations.
|
||||
*
|
||||
|
@ -111,11 +129,51 @@ public class DaoConfig {
|
|||
public long getSubscriptionPollDelay() {
|
||||
return mySubscriptionPollDelay;
|
||||
}
|
||||
|
||||
public Long getSubscriptionPurgeInactiveAfterMillis() {
|
||||
return mySubscriptionPurgeInactiveAfterMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* This setting may be used to advise the server that any references found in
|
||||
* resources that have any of the base URLs given here will be replaced with
|
||||
* simple local references.
|
||||
* <p>
|
||||
* For example, if the set contains the value <code>http://example.com/base/</code>
|
||||
* and a resource is submitted to the server that contains a reference to
|
||||
* <code>http://example.com/base/Patient/1</code>, the server will automatically
|
||||
* convert this reference to <code>Patient/1</code>
|
||||
* </p>
|
||||
*/
|
||||
public Set<String> getTreatBaseUrlsAsLocal() {
|
||||
return myTreatBaseUrlsAsLocal;
|
||||
}
|
||||
/**
|
||||
* If set to <code>true</code> (default is <code>false</code>) the server will allow
|
||||
* resources to have references to external servers. For example if this server is
|
||||
* running at <code>http://example.com/fhir</code> and this setting is set to
|
||||
* <code>true</code> the server will allow a Patient resource to be saved with a
|
||||
* Patient.organization value of <code>http://foo.com/Organization/1</code>.
|
||||
* <p>
|
||||
* Under the default behaviour if this value has not been changed, the above
|
||||
* resource would be rejected by the server because it requires all references
|
||||
* to be resolvable on the local server.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that external references will be indexed by the server and may be searched
|
||||
* (e.g. <code>Patient:organization</code>), but
|
||||
* chained searches (e.g. <code>Patient:organization.name</code>) will not work across
|
||||
* these references.
|
||||
* </p>
|
||||
* <p>
|
||||
* It is recommended to also set {@link #setTreatBaseUrlsAsLocal(Set)} if this value
|
||||
* is set to <code>true</code>
|
||||
* </p>
|
||||
*
|
||||
* @see #setTreatBaseUrlsAsLocal(Set)
|
||||
* @see #setAllowExternalReferences(boolean)
|
||||
*/
|
||||
public boolean isAllowExternalReferences() {
|
||||
return myAllowExternalReferences;
|
||||
}
|
||||
/**
|
||||
* @see #setAllowInlineMatchUrlReferences(boolean)
|
||||
*/
|
||||
|
@ -146,6 +204,35 @@ public class DaoConfig {
|
|||
return mySubscriptionEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (default is <code>false</code>) the server will allow
|
||||
* resources to have references to external servers. For example if this server is
|
||||
* running at <code>http://example.com/fhir</code> and this setting is set to
|
||||
* <code>true</code> the server will allow a Patient resource to be saved with a
|
||||
* Patient.organization value of <code>http://foo.com/Organization/1</code>.
|
||||
* <p>
|
||||
* Under the default behaviour if this value has not been changed, the above
|
||||
* resource would be rejected by the server because it requires all references
|
||||
* to be resolvable on the local server.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that external references will be indexed by the server and may be searched
|
||||
* (e.g. <code>Patient:organization</code>), but
|
||||
* chained searches (e.g. <code>Patient:organization.name</code>) will not work across
|
||||
* these references.
|
||||
* </p>
|
||||
* <p>
|
||||
* It is recommended to also set {@link #setTreatBaseUrlsAsLocal(Set)} if this value
|
||||
* is set to <code>true</code>
|
||||
* </p>
|
||||
*
|
||||
* @see #setTreatBaseUrlsAsLocal(Set)
|
||||
* @see #setAllowExternalReferences(boolean)
|
||||
*/
|
||||
public void setAllowExternalReferences(boolean theAllowExternalReferences) {
|
||||
myAllowExternalReferences = theAllowExternalReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should references containing match URLs be resolved and replaced in create and update operations. For
|
||||
* example, if this property is set to true and a resource is created containing a reference
|
||||
|
@ -165,8 +252,14 @@ public class DaoConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Search results are stored in the database so that they can be paged through. After this
|
||||
* number of milliseconds, they will be deleted from the database. Defaults to 1 hour.
|
||||
* Sets the number of milliseconds that search results for a given client search
|
||||
* should be preserved before being purged from the database.
|
||||
* <p>
|
||||
* Search results are stored in the database so that they can be paged over multiple
|
||||
* requests. After this
|
||||
* number of milliseconds, they will be deleted from the database, and any paging links
|
||||
* (next/prev links in search response bundles) will become invalid. Defaults to 1 hour.
|
||||
* </p>
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
|
@ -178,6 +271,9 @@ public class DaoConfig {
|
|||
myHardSearchLimit = theHardSearchLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of results to return in a GetTags query (DSTU1 only)
|
||||
*/
|
||||
public void setHardTagListLimit(int theHardTagListLimit) {
|
||||
myHardTagListLimit = theHardTagListLimit;
|
||||
}
|
||||
|
@ -246,8 +342,8 @@ public class DaoConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Does this server support subscription? If set to true, the server will enable the subscription monitoring mode,
|
||||
* which adds a bit of overhead. Note that if this is enabled, you must also include Spring task scanning to your XML
|
||||
* If set to true, the server will enable support for subscriptions. Subscriptions
|
||||
* will by default be handled via a polling task. Note that if this is enabled, you must also include Spring task scanning to your XML
|
||||
* config for the scheduled tasks used by the subscription module.
|
||||
*/
|
||||
public void setSubscriptionEnabled(boolean theSubscriptionEnabled) {
|
||||
|
@ -269,4 +365,29 @@ public class DaoConfig {
|
|||
setSubscriptionPurgeInactiveAfterMillis(theSeconds * DateUtils.MILLIS_PER_SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* This setting may be used to advise the server that any references found in
|
||||
* resources that have any of the base URLs given here will be replaced with
|
||||
* simple local references.
|
||||
* <p>
|
||||
* For example, if the set contains the value <code>http://example.com/base/</code>
|
||||
* and a resource is submitted to the server that contains a reference to
|
||||
* <code>http://example.com/base/Patient/1</code>, the server will automatically
|
||||
* convert this reference to <code>Patient/1</code>
|
||||
* </p>
|
||||
*
|
||||
* @param theTreatBaseUrlsAsLocal The set of base URLs. May be <code>null</code>, which
|
||||
* means no references will be treated as external
|
||||
*/
|
||||
public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) {
|
||||
HashSet<String> treatBaseUrlsAsLocal = new HashSet<String>();
|
||||
for (String next : ObjectUtils.defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
|
||||
while (next.endsWith("/")) {
|
||||
next = next.substring(0, next.length() - 1);
|
||||
}
|
||||
treatBaseUrlsAsLocal.add(next);
|
||||
}
|
||||
myTreatBaseUrlsAsLocal = treatBaseUrlsAsLocal;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ import org.springframework.transaction.TransactionStatus;
|
|||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
|
@ -577,9 +578,26 @@ public class SearchBuilder {
|
|||
ReferenceParam ref = (ReferenceParam) params;
|
||||
|
||||
if (isBlank(ref.getChain())) {
|
||||
String resourceId = ref.getValueAsQueryToken(myContext);
|
||||
IIdType dt = new IdDt(resourceId);
|
||||
List<Long> targetPid = myCallingDao.translateForcedIdToPids(dt);
|
||||
IIdType dt = new IdDt(ref.getBaseUrl(), ref.getResourceType(), ref.getIdPart(), null);
|
||||
|
||||
if (dt.hasBaseUrl()) {
|
||||
if (myCallingDao.getConfig().getTreatBaseUrlsAsLocal().contains(dt.getBaseUrl())) {
|
||||
dt = dt.toUnqualified();
|
||||
} else {
|
||||
ourLog.debug("Searching for resource link with target URL: {}", dt.getValue());
|
||||
Predicate eq = builder.equal(from.get("myTargetResourceUrl"), dt.getValue());
|
||||
codePredicates.add(eq);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
List<Long> targetPid;
|
||||
try {
|
||||
targetPid = myCallingDao.translateForcedIdToPids(dt);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
doSetPids(new ArrayList<Long>());
|
||||
return;
|
||||
}
|
||||
for (Long next : targetPid) {
|
||||
ourLog.debug("Searching for resource link with target PID: {}", next);
|
||||
Predicate eq = builder.equal(from.get("myTargetResourcePid"), next);
|
||||
|
|
|
@ -38,6 +38,7 @@ 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;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
@Entity
|
||||
@Table(name = "HFJ_RES_LINK" , indexes= {
|
||||
|
@ -70,11 +71,11 @@ public class ResourceLink implements Serializable {
|
|||
@Field()
|
||||
private String mySourceResourceType;
|
||||
|
||||
@ManyToOne(optional = false, fetch=FetchType.LAZY)
|
||||
@JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = false)
|
||||
@ManyToOne(optional = true, fetch=FetchType.LAZY)
|
||||
@JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = true)
|
||||
private ResourceTable myTargetResource;
|
||||
|
||||
@Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false, nullable = false)
|
||||
@Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false, nullable = true)
|
||||
@Field()
|
||||
private Long myTargetResourcePid;
|
||||
|
||||
|
@ -83,6 +84,10 @@ public class ResourceLink implements Serializable {
|
|||
@Field()
|
||||
private String myTargetResourceType;
|
||||
|
||||
@Column(name = "TARGET_RESOURCE_URL", length=200, nullable = true)
|
||||
@Field()
|
||||
private String myTargetResourceUrl;
|
||||
|
||||
public ResourceLink() {
|
||||
super();
|
||||
}
|
||||
|
@ -94,6 +99,13 @@ public class ResourceLink implements Serializable {
|
|||
setTargetResource(theTargetResource);
|
||||
}
|
||||
|
||||
public ResourceLink(String theSourcePath, ResourceTable theSourceResource, IIdType theTargetResourceUrl) {
|
||||
super();
|
||||
setSourcePath(theSourcePath);
|
||||
setSourceResource(theSourceResource);
|
||||
setTargetResourceUrl(theTargetResourceUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object theObj) {
|
||||
if (this == theObj) {
|
||||
|
@ -110,6 +122,7 @@ public class ResourceLink implements Serializable {
|
|||
b.append(mySourcePath, obj.mySourcePath);
|
||||
b.append(mySourceResource, obj.mySourceResource);
|
||||
b.append(myTargetResourcePid, obj.myTargetResourcePid);
|
||||
b.append(myTargetResourceUrl, obj.myTargetResourceUrl);
|
||||
return b.isEquals();
|
||||
}
|
||||
|
||||
|
@ -133,12 +146,17 @@ public class ResourceLink implements Serializable {
|
|||
return myTargetResourcePid;
|
||||
}
|
||||
|
||||
public String getTargetResourceUrl() {
|
||||
return myTargetResourceUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
HashCodeBuilder b = new HashCodeBuilder();
|
||||
b.append(mySourcePath);
|
||||
b.append(mySourceResource);
|
||||
b.append(myTargetResourcePid);
|
||||
b.append(myTargetResourceUrl);
|
||||
return b.toHashCode();
|
||||
}
|
||||
|
||||
|
@ -159,6 +177,15 @@ public class ResourceLink implements Serializable {
|
|||
myTargetResourceType = theTargetResource.getResourceType();
|
||||
}
|
||||
|
||||
public void setTargetResourceUrl(IIdType theTargetResourceUrl) {
|
||||
Validate.isTrue(theTargetResourceUrl.hasBaseUrl());
|
||||
Validate.isTrue(theTargetResourceUrl.hasResourceType());
|
||||
Validate.isTrue(theTargetResourceUrl.hasIdPart());
|
||||
|
||||
myTargetResourceType = theTargetResourceUrl.getResourceType();
|
||||
myTargetResourceUrl = theTargetResourceUrl.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
@ -166,6 +193,7 @@ public class ResourceLink implements Serializable {
|
|||
b.append("path=").append(mySourcePath);
|
||||
b.append(", src=").append(mySourceResourcePid);
|
||||
b.append(", target=").append(myTargetResourcePid);
|
||||
b.append(", targetUrl=").append(myTargetResourceUrl);
|
||||
|
||||
b.append("]");
|
||||
return b.toString();
|
||||
|
|
|
@ -149,8 +149,8 @@ public class ResourceTable extends BaseHasResource implements Serializable {
|
|||
private boolean myHasLinks;
|
||||
|
||||
@Id
|
||||
@SequenceGenerator(name="SEQ_RESOURCE_ID", sequenceName="SEQ_RESOURCE_ID")
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_RESOURCE_ID")
|
||||
@SequenceGenerator(name = "SEQ_RESOURCE_ID", sequenceName = "SEQ_RESOURCE_ID")
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESOURCE_ID")
|
||||
@Column(name = "RES_ID")
|
||||
private Long myId;
|
||||
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Organization;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public class FhirResourceDaoDstu3ExternalReferenceTest extends BaseJpaDstu3Test {
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@Before
|
||||
@After
|
||||
public void resetDefaultBehaviour() {
|
||||
// Reset to default
|
||||
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
|
||||
myDaoConfig.setTreatBaseUrlsAsLocal(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternalReferenceBlockedByDefault() {
|
||||
Patient p = new Patient();
|
||||
p.getManagingOrganization().setReference("Organization/FOO");
|
||||
try {
|
||||
myPatientDao.create(p, mySrd);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Resource Organization/FOO not found, specified in path: Patient.managingOrganization", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalReferenceBlockedByDefault() {
|
||||
Organization org = new Organization();
|
||||
org.setId("FOO");
|
||||
org.setName("Org Name");
|
||||
myOrganizationDao.update(org, mySrd);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.getManagingOrganization().setReference("http://example.com/base/Organization/FOO");
|
||||
try {
|
||||
myPatientDao.create(p, mySrd);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Resource contains external reference to URL \"http://example.com/base/Organization/FOO\" but this server is not configured to allow external references", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalReferenceAllowed() {
|
||||
Organization org = new Organization();
|
||||
org.setId("FOO");
|
||||
org.setName("Org Name");
|
||||
myOrganizationDao.update(org, mySrd);
|
||||
|
||||
myDaoConfig.setAllowExternalReferences(true);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.getManagingOrganization().setReference("http://example.com/base/Organization/FOO");
|
||||
IIdType pid = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(Patient.SP_ORGANIZATION, new ReferenceParam("http://example.com/base/Organization/FOO"));
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(pid.getValue()));
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add(Patient.SP_ORGANIZATION, new ReferenceParam("http://example2.com/base/Organization/FOO"));
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalReferenceReplaced() {
|
||||
Organization org = new Organization();
|
||||
org.setId("FOO");
|
||||
org.setName("Org Name");
|
||||
org.getPartOf().setDisplay("Parent"); // <-- no reference, make sure this works
|
||||
myOrganizationDao.update(org, mySrd);
|
||||
|
||||
Set<String> urls = new HashSet<String>();
|
||||
urls.add("http://example.com/base/");
|
||||
myDaoConfig.setTreatBaseUrlsAsLocal(urls);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.getManagingOrganization().setReference("http://example.com/base/Organization/FOO");
|
||||
IIdType pid = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
p = myPatientDao.read(pid, mySrd);
|
||||
assertEquals("Organization/FOO", p.getManagingOrganization().getReference());
|
||||
|
||||
SearchParameterMap map;
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add(Patient.SP_ORGANIZATION, new ReferenceParam("http://example.com/base/Organization/FOO"));
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(pid.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchForInvalidLocalReference() {
|
||||
SearchParameterMap map;
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add(Patient.SP_ORGANIZATION, new ReferenceParam("Organization/FOO"));
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty());
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add(Patient.SP_ORGANIZATION, new ReferenceParam("Organization/9999999999"));
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalReferenceReplacedWrongDoesntMatch() {
|
||||
Organization org = new Organization();
|
||||
org.setId("FOO");
|
||||
org.setName("Org Name");
|
||||
org.getPartOf().setDisplay("Parent"); // <-- no reference, make sure this works
|
||||
myOrganizationDao.update(org, mySrd);
|
||||
|
||||
Set<String> urls = new HashSet<String>();
|
||||
urls.add("http://example.com/base/");
|
||||
myDaoConfig.setTreatBaseUrlsAsLocal(urls);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.getManagingOrganization().setReference("http://example.com/base/Organization/FOO");
|
||||
IIdType pid = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
p = myPatientDao.read(pid, mySrd);
|
||||
assertEquals("Organization/FOO", p.getManagingOrganization().getReference());
|
||||
|
||||
SearchParameterMap map;
|
||||
|
||||
// Different base
|
||||
map = new SearchParameterMap();
|
||||
map.add(Patient.SP_ORGANIZATION, new ReferenceParam("http://foo.com/base/Organization/FOO"));
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty());
|
||||
}
|
||||
|
||||
}
|
|
@ -43,6 +43,9 @@ public class TestDstu1Config extends BaseJavaConfigDstu1 {
|
|||
DaoConfig retVal = new DaoConfig();
|
||||
retVal.setSubscriptionEnabled(false);
|
||||
retVal.setAllowMultipleDelete(false);
|
||||
retVal.setAllowExternalReferences(true);
|
||||
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu1");
|
||||
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu1");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
|
|||
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
|
||||
retVal.setAllowMultipleDelete(true);
|
||||
retVal.setAllowInlineMatchUrlReferences(true);
|
||||
retVal.setAllowExternalReferences(true);
|
||||
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu2");
|
||||
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu2");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,9 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
|||
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
|
||||
retVal.setAllowMultipleDelete(true);
|
||||
retVal.setAllowInlineMatchUrlReferences(true);
|
||||
retVal.setAllowExternalReferences(true);
|
||||
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu3");
|
||||
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu3");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -197,6 +197,12 @@
|
|||
search parameters that did not start with an underscore. E.g. "Patient?_id=1" failed
|
||||
even though this is a valid conditional reference.
|
||||
</action>
|
||||
<action type="add" issue="363">
|
||||
JPA server can now be configured to allow external references (i.e. references that
|
||||
point to resources on other servers). See
|
||||
<![CDATA[<a href="./doc_jpa.html">JPA Documentation</a>]]> for information on
|
||||
how to use this. Thanks to Naminder Soorma for the suggestion!
|
||||
</action>
|
||||
</release>
|
||||
<release version="1.5" date="2016-04-20">
|
||||
<action type="fix" issue="339">
|
||||
|
|
|
@ -147,6 +147,89 @@ $ mvn install]]></source>
|
|||
Configures the database connection settings
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="DaoConfig">
|
||||
|
||||
<p>
|
||||
The Spring confguration contains a definition for a bean called <code>daoConfig</code>,
|
||||
which will look something like the following:
|
||||
</p>
|
||||
<pre><![CDATA[@Bean()
|
||||
public DaoConfig daoConfig() {
|
||||
DaoConfig retVal = new DaoConfig();
|
||||
retVal.setAllowMultipleDelete(true);
|
||||
retVal.setAllowInlineMatchUrlReferences(true);
|
||||
return retVal;
|
||||
}]]></pre>
|
||||
|
||||
<p>
|
||||
You can use this method to change various configuration settings on the DaoConfig bean
|
||||
which define the way that the JPA server will behave.
|
||||
See the <a href="./apidocs-jpaserver/ca/uhn/fhir/jpa/dao/DaoConfig.html">DaoConfig JavaDoc</a>
|
||||
for information about the available settings.
|
||||
</p>
|
||||
|
||||
<subsection name="External/Absolute Resource References">
|
||||
|
||||
<p>
|
||||
Clients may sometimes post resources to your server that contain
|
||||
absolute resource references. For example, consider the following resource:
|
||||
</p>
|
||||
<pre><![CDATA[<Patient xmlns="http://hl7.org/fhir">
|
||||
<id value="patient-infant-01"/>
|
||||
<name>
|
||||
<use value="official"/>
|
||||
<family value="Miller"/>
|
||||
<given value="Samuel"/>
|
||||
</name>
|
||||
<managingOrganization>
|
||||
<reference value="http://example.com/fhir/Organization/123"/>
|
||||
</managingOrganization>
|
||||
</Patient>]]></pre>
|
||||
|
||||
<p>
|
||||
By default, the server will reject this reference, as only
|
||||
local references are permitted by the server. This can be changed
|
||||
however.
|
||||
</p>
|
||||
<p>
|
||||
If you want the server to recognize that this URL is actually a local
|
||||
reference (i.e. because the server will be deployed to the base URL
|
||||
<code>http://example.com/fhir/</code>) you can
|
||||
configure the server to recognize this URL via the following DaoConfig
|
||||
setting:
|
||||
</p>
|
||||
<pre><![CDATA[@Bean()
|
||||
public DaoConfig daoConfig() {
|
||||
DaoConfig retVal = new DaoConfig();
|
||||
// ... other config ...
|
||||
retVal.getTreatBaseUrlsAsLocal().add("http://example.com/fhir/");
|
||||
return retVal;
|
||||
}]]></pre>
|
||||
|
||||
<p>
|
||||
On the other hand, if you want the server to be configurable to
|
||||
allow remote references, you can set this with the confguration below.
|
||||
Using the <code>setAllowInlineMatchUrlReferences</code> means that
|
||||
it will be possible to search for references that refer to these
|
||||
external references.
|
||||
</p>
|
||||
|
||||
<pre><![CDATA[@Bean()
|
||||
public DaoConfig daoConfig() {
|
||||
DaoConfig retVal = new DaoConfig();
|
||||
// Allow external references
|
||||
retVal.setAllowInlineMatchUrlReferences(true);
|
||||
|
||||
// If you are allowing external references, it is recommended to
|
||||
// also tell the server which references actually will be local
|
||||
retVal.getTreatBaseUrlsAsLocal().add("http://mydomain.com/fhir");
|
||||
return retVal;
|
||||
}]]></pre>
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Not yet complete">
|
||||
|
|
Loading…
Reference in New Issue