Support $validate operation correctly in DSTU2 clients and in testpage

overlay
This commit is contained in:
James Agnew 2015-06-16 11:56:30 -04:00
parent 81bfc28147
commit 6f7ef96b97
33 changed files with 1076 additions and 98 deletions

View File

@ -283,6 +283,11 @@ public class IdDt extends UriDt implements IPrimitiveDatatype<String>, IIdType {
@Override
public String getValue() {
if (super.getValue() == null && myHaveComponentParts) {
if (determineLocalPrefix(myBaseUrl) != null && myResourceType == null && myUnqualifiedVersionId == null) {
return myBaseUrl + myUnqualifiedId;
}
StringBuilder b = new StringBuilder();
if (isNotBlank(myBaseUrl)) {
b.append(myBaseUrl);
@ -395,11 +400,15 @@ public class IdDt extends UriDt implements IPrimitiveDatatype<String>, IIdType {
}
/**
* Returns <code>true</code> if the ID is a local reference (in other words, it begins with the '#' character)
* Returns <code>true</code> if the ID is a local reference (in other words, it begins with the '#' character
* or it begins with "cid:" or "urn:")
*/
@Override
public boolean isLocal() {
return myUnqualifiedId != null && myUnqualifiedId.isEmpty() == false && myUnqualifiedId.charAt(0) == '#';
if (myBaseUrl == null) {
return false;
}
return "#".equals(myBaseUrl) || myBaseUrl.equals("cid:") || myBaseUrl.startsWith("urn:");
}
/**
@ -410,6 +419,34 @@ public class IdDt extends UriDt implements IPrimitiveDatatype<String>, IIdType {
setValue(theId.getValue());
}
private String determineLocalPrefix(String theValue) {
if (theValue == null || theValue.isEmpty()) {
return null;
}
if (theValue.startsWith("#")) {
return "#";
}
int lastPrefix = -1;
for (int i = 0; i < theValue.length(); i++) {
char nextChar = theValue.charAt(i);
if (nextChar == ':') {
lastPrefix = i;
} else if (!Character.isLetter(nextChar) || !Character.isLowerCase(nextChar)) {
break;
}
}
if (lastPrefix != -1) {
String candidate = theValue.substring(0, lastPrefix + 1);
if (candidate.startsWith("cid:") || candidate.startsWith("urn:")) {
return candidate;
} else {
return null;
}
} else {
return null;
}
}
/**
* Set the value
*
@ -426,15 +463,25 @@ public class IdDt extends UriDt implements IPrimitiveDatatype<String>, IIdType {
// TODO: add validation
super.setValue(theValue);
myHaveComponentParts = false;
String localPrefix = determineLocalPrefix(theValue);
if (StringUtils.isBlank(theValue)) {
myBaseUrl = null;
super.setValue(null);
myUnqualifiedId = null;
myUnqualifiedVersionId = null;
myResourceType = null;
} else if (theValue.charAt(0) == '#') {
} else if (theValue.charAt(0) == '#' && theValue.length() > 1) {
super.setValue(theValue);
myUnqualifiedId = theValue;
myBaseUrl = "#";
myUnqualifiedId = theValue.substring(1);
myUnqualifiedVersionId = null;
myResourceType = null;
myHaveComponentParts = true;
} else if (localPrefix != null) {
myBaseUrl = localPrefix;
myUnqualifiedId = theValue.substring(localPrefix.length());
myUnqualifiedVersionId = null;
myResourceType = null;
myHaveComponentParts = true;

View File

@ -186,7 +186,7 @@ public abstract class BaseClient implements IRestfulClient {
encoding=theEncoding;
}
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding);
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
if (theLogRequestAndResponse) {
ourLog.info("Client invoking: {}", httpRequest);

View File

@ -56,7 +56,7 @@ public abstract class BaseHttpClientInvocation {
* The encoding to use for any serialized content sent to the
* server
*/
public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding);
public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint);
protected static void appendExtraParamsWithQuestionMark(Map<String, List<String>> theExtraParams, StringBuilder theUrlBuilder, boolean theWithQuestionMark) {
if (theExtraParams == null) {

View File

@ -158,7 +158,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation();
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
}
@SuppressWarnings("unchecked")
@ -178,7 +178,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public MethodOutcome create(IResource theResource) {
BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(theResource, myContext);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
}
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
@ -200,7 +200,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public MethodOutcome delete(final Class<? extends IResource> theType, IdDt theId) {
HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType)));
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
}
final String resourceName = myContext.getResourceDefinition(theType).getName();
@ -236,7 +236,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
}
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
}
if (theIfVersionMatches != null) {
@ -312,7 +312,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
String id = theIdDt != null && theIdDt.isEmpty() == false ? theIdDt.getValue() : null;
HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, theSince, theLimit);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
}
BundleResponseHandler binding = new BundleResponseHandler(theType);
@ -430,7 +430,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
BaseHttpClientInvocation invocation = SearchMethodBinding.createSearchInvocation(myContext, toResourceName(theType), params, null, null, null);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
}
BundleResponseHandler binding = new BundleResponseHandler(theType);
@ -474,7 +474,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public List<IBaseResource> transaction(List<IBaseResource> theResources) {
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(theResources, myContext);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
}
Bundle resp = invokeClient(myContext, new BundleResponseHandler(null), invocation, myLogRequestAndResponse);
@ -491,7 +491,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public MethodOutcome update(IdDt theIdDt, IResource theResource) {
BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
}
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
@ -522,7 +522,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
}
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
@ -601,7 +601,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
// }
if (isKeepResponses()) {
myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding());
myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint);
}
Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse);

View File

@ -188,7 +188,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
}
@Override
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding) throws DataFormatException {
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) throws DataFormatException {
StringBuilder url = new StringBuilder();
if (myUrlPath == null) {
@ -236,6 +236,10 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
parser = myContext.newXmlParser();
}
if (thePrettyPrint != null) {
parser.setPrettyPrint(thePrettyPrint);
}
parser.setOmitResourceId(myOmitResourceId);
AbstractHttpEntity entity;

View File

@ -50,7 +50,7 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation {
}
@Override
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding) {
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) {
StringBuilder b = new StringBuilder();
b.append(theUrlBase);
if (!theUrlBase.endsWith("/")) {

View File

@ -79,7 +79,7 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation {
}
@Override
public HttpGet asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding) {
public HttpGet asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) {
StringBuilder b = new StringBuilder();
if (!myUrlPath.contains("://")) {

View File

@ -38,7 +38,7 @@ public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation {
}
@Override
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding) {
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) {
HttpGet retVal = new HttpGet(myUrl);
super.addHeadersToRequest(retVal);
return retVal;

View File

@ -85,7 +85,11 @@ public class ReferenceParam extends IdDt implements IQueryParameterType {
if (myBase.getMissing()!=null) {
return myBase.getValueAsQueryToken();
}
return getIdPart();
if (isLocal()) {
return getValue();
} else {
return getIdPart();
}
}
public void setChain(String theChain) {

View File

@ -1048,8 +1048,8 @@ public abstract class BaseFhirDao implements IDao {
quantityParams = extractSearchParamQuantity(entity, theResource);
dateParams = extractSearchParamDates(entity, theResource);
ourLog.info("Indexing resource: {}", entity.getId());
ourLog.info("Storing string indexes: {}", stringParams);
// ourLog.info("Indexing resource: {}", entity.getId());
ourLog.trace("Storing string indexes: {}", stringParams);
tokenParams = new ArrayList<ResourceIndexedSearchParamToken>();
for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(entity, theResource)) {

View File

@ -574,12 +574,12 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
ReferenceParam ref = (ReferenceParam) params;
String resourceId = ref.getValueAsQueryToken();
if (resourceId.contains("/")) {
IdDt dt = new IdDt(resourceId);
resourceId = dt.getIdPart();
}
if (isBlank(ref.getChain())) {
if (resourceId.contains("/")) {
IdDt dt = new IdDt(resourceId);
resourceId = dt.getIdPart();
}
Long targetPid = translateForcedIdToPid(new IdDt(resourceId));
ourLog.info("Searching for resource link with target PID: {}", targetPid);
Predicate eq = builder.equal(from.get("myTargetResourcePid"), targetPid);
@ -1097,6 +1097,9 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing) {
StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource);
ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource));
@ -1112,7 +1115,7 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
}
}
if (theResource.getId().isEmpty() == false) {
if (isNotBlank(theResource.getId().getIdPart())) {
if (isValidPid(theResource.getId())) {
throw new UnprocessableEntityException(
"This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
@ -1139,6 +1142,10 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return outcome;
}
protected void preProcessResourceForStorage(T theResource) {
// nothing by default
}
@Override
public TagList getAllResourceTags() {
StopWatch w = new StopWatch();
@ -1995,6 +2002,8 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing) {
StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource);
final ResourceTable entity;
IdDt resourceId;

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class FhirBundleResourceDaoDstu2 extends FhirResourceDaoDstu2<Bundle> {
@Override
protected void preProcessResourceForStorage(Bundle theResource) {
super.preProcessResourceForStorage(theResource);
if (theResource.getTypeElement().getValueAsEnum() != BundleTypeEnum.DOCUMENT) {
String message = "Unable to store a Bundle resource on this server with a Bundle.type value other than '" + BundleTypeEnum.DOCUMENT.getCode() + "' - Value was: " + (theResource.getTypeElement().getValueAsEnum() != null ? theResource.getTypeElement().getValueAsEnum().getCode() : "(missing)");
throw new UnprocessableEntityException(message);
}
theResource.setBase((UriDt)null);
}
}

View File

@ -60,14 +60,18 @@ public class FhirSystemDaoDstu1 extends BaseFhirSystemDao<List<IResource>> {
for (int i = 0; i < theResources.size(); i++) {
IResource res = theResources.get(i);
if (res.getId().hasIdPart() && !res.getId().hasResourceType()) {
if (res.getId().hasIdPart() && !res.getId().hasResourceType() && !res.getId().isLocal()) {
res.setId(new IdDt(toResourceName(res.getClass()), res.getId().getIdPart()));
}
/*
* Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness
*/
if (res.getId().hasResourceType() && res.getId().hasIdPart()) {
if (res.getId().isLocal()) {
if (!allIds.add(res.getId())) {
throw new InvalidRequestException("Transaction bundle contains multiple resources with ID: " + res.getId());
}
} else if (res.getId().hasResourceType() && res.getId().hasIdPart()) {
IdDt nextId = res.getId().toUnqualifiedVersionless();
if (!allIds.add(nextId)) {
throw new InvalidRequestException("Transaction bundle contains multiple resources with ID: " + nextId);
@ -134,7 +138,7 @@ public class FhirSystemDaoDstu1 extends BaseFhirSystemDao<List<IResource>> {
} else {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionOperationWithMultipleMatchFailure", nextResouceOperationIn.name(), matchUrl, candidateMatches.size()));
}
} else if (nextId.isEmpty()) {
} else if (nextId.isEmpty() || nextId.isLocal()) {
entity = null;
} else {
entity = tryToLoadEntity(nextId);
@ -144,7 +148,7 @@ public class FhirSystemDaoDstu1 extends BaseFhirSystemDao<List<IResource>> {
if (entity == null) {
nextResouceOperationOut = BundleEntryTransactionMethodEnum.POST;
entity = toEntity(nextResource);
if (nextId.isEmpty() == false && nextId.getIdPart().startsWith("cid:")) {
if (nextId.isEmpty() == false && "cid:".equals(nextId.getBaseUrl())) {
ourLog.debug("Resource in transaction has ID[{}], will replace with server assigned ID", nextId.getIdPart());
} else if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.POST) {
if (nextId.isEmpty() == false) {
@ -209,11 +213,11 @@ public class FhirSystemDaoDstu1 extends BaseFhirSystemDao<List<IResource>> {
if (nextId.toUnqualifiedVersionless().equals(newId)) {
ourLog.info("Transaction resource ID[{}] is being updated", newId);
} else {
if (!nextId.getIdPart().startsWith("#")) {
nextId = new IdDt(resourceName, nextId.getIdPart());
if (nextId.isLocal()) {
// nextId = new IdDt(resourceName, nextId.getIdPart());
ourLog.info("Transaction resource ID[{}] has been assigned new ID[{}]", nextId, newId);
idConversions.put(nextId, newId);
idConversions.put(new IdDt(nextId.getIdPart()), newId);
idConversions.put(new IdDt(resourceName + "/" + nextId.getValue()), newId);
}
}
}

View File

@ -151,7 +151,7 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao<Bundle> {
if (res != null) {
nextResourceId = res.getId();
if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType()) {
if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !nextResourceId.isLocal()) {
nextResourceId = new IdDt(toResourceName(res.getClass()), nextResourceId.getIdPart());
res.setId(nextResourceId);
}
@ -159,7 +159,11 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao<Bundle> {
/*
* Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness
*/
if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) {
if (nextResourceId.isLocal()) {
if (!allIds.add(nextResourceId)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId));
}
} else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) {
IdDt nextId = nextResourceId.toUnqualifiedVersionless();
if (!allIds.add(nextId)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId));
@ -173,6 +177,8 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao<Bundle> {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionEntryHasInvalidVerb", nextEntry.getTransaction().getMethod()));
}
String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null;
switch (verb) {
case POST: {
// CREATE
@ -182,7 +188,7 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao<Bundle> {
DaoMethodOutcome outcome;
Entry newEntry = response.addEntry();
outcome = resourceDao.create(res, nextEntry.getTransaction().getIfNoneExist(), false);
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry);
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry, resourceType);
break;
}
case DELETE: {
@ -218,7 +224,7 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao<Bundle> {
outcome = resourceDao.update(res, parts.getResourceType() + '?' + parts.getParams(), false);
}
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry);
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, newEntry, resourceType);
break;
}
case GET: {
@ -249,7 +255,8 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao<Bundle> {
int configuredMax = 100; // this should probably be configurable or something
if (bundle.size() > configuredMax) {
oo.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails("Search nested within transaction found more than " + configuredMax + " matches, but paging is not supported in nested transactions");
oo.addIssue().setSeverity(IssueSeverityEnum.WARNING)
.setDetails("Search nested within transaction found more than " + configuredMax + " matches, but paging is not supported in nested transactions");
}
List<IBaseResource> resourcesToAdd = bundle.getResources(0, Math.min(bundle.size(), configuredMax));
for (IBaseResource next : resourcesToAdd) {
@ -330,16 +337,18 @@ public class FhirSystemDaoDstu2 extends BaseFhirSystemDao<Bundle> {
return url;
}
private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, Entry newEntry) {
private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome,
Entry newEntry, String theResourceType) {
IdDt newId = outcome.getId().toUnqualifiedVersionless();
IdDt resourceId = nextResourceId.toUnqualifiedVersionless();
IdDt resourceId = nextResourceId.isLocal() ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
if (newId.equals(resourceId) == false) {
/*
* The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified
* kind too just to be lenient.
*/
idSubstitutions.put(resourceId, newId);
idSubstitutions.put(resourceId.withResourceType(null), newId);
if (resourceId.isLocal()) {
/*
* The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified kind too just to be lenient.
*/
idSubstitutions.put(new IdDt(theResourceType + '/' + resourceId.getValue()), newId);
}
}
idToPersistedOutcome.put(newId, outcome);
if (outcome.getCreated().booleanValue()) {

View File

@ -50,17 +50,17 @@ public class ResourceLink implements Serializable {
private String mySourcePath;
@ManyToOne(optional = false)
@JoinColumn(name = "SRC_RESOURCE_ID", referencedColumnName="RES_ID")
@JoinColumn(name = "SRC_RESOURCE_ID", referencedColumnName="RES_ID", nullable=false)
private ResourceTable mySourceResource;
@Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false)
@Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable=false)
private Long mySourceResourcePid;
@ManyToOne(optional = false)
@JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName="RES_ID")
@JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName="RES_ID", nullable=false)
private ResourceTable myTargetResource;
@Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false)
@Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false,nullable=false)
private Long myTargetResourcePid;
public ResourceLink() {

View File

@ -154,7 +154,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
}
@Validate
public MethodOutcome validate(@ResourceParam T theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
public MethodOutcome validate(@ResourceParam T theResource, @IdParam IdDt theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
@Validate.Profile String theProfile) {
final OperationOutcome oo = new OperationOutcome();

View File

@ -1395,6 +1395,12 @@ public class FhirResourceDaoDstu2Test {
assertEquals(1, result.size());
assertEquals(obsId01.getIdPart(), result.get(0).getId().getIdPart());
result = toList(ourObservationDao.search(Observation.SP_PATIENT, new ReferenceParam(patientId01.getIdPart())));
assertEquals(1, result.size());
result = toList(ourObservationDao.search(Observation.SP_PATIENT, new ReferenceParam(patientId01.getIdPart())));
assertEquals(1, result.size());
result = toList(ourObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "999999999999")));
assertEquals(0, result.size());

View File

@ -358,7 +358,10 @@ public class FhirSystemDaoDstu1Test {
List<IResource> response = ourSystemDao.transaction(res);
ourLog.info(ourFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(response.get(0)));
String encodeResourceToString = ourFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(response.get(0));
ourLog.info(encodeResourceToString);
assertThat(encodeResourceToString, not(containsString("smsp")));
}
/**

View File

@ -846,7 +846,7 @@ public class FhirSystemDaoDstu2Test {
assertEquals(OperationOutcome.class, resp.getEntry().get(0).getResource().getClass());
OperationOutcome outcome = (OperationOutcome) resp.getEntry().get(0).getResource();
assertThat(outcome.getIssue().get(1).getDetails(), containsString("Placeholder resource ID \"Patient/urn:oid:0.1.2.3\" was replaced with permanent ID \"Patient/"));
assertThat(outcome.getIssue().get(1).getDetails(), containsString("Placeholder resource ID \"urn:oid:0.1.2.3\" was replaced with permanent ID \"Patient/"));
assertTrue(resp.getEntry().get(1).getTransactionResponse().getLocation(), new IdDt(resp.getEntry().get(1).getTransactionResponse().getLocation()).getIdPart().matches("^[0-9]+$"));
assertTrue(resp.getEntry().get(2).getTransactionResponse().getLocation(), new IdDt(resp.getEntry().get(2).getTransactionResponse().getLocation()).getIdPart().matches("^[0-9]+$"));
@ -894,7 +894,7 @@ public class FhirSystemDaoDstu2Test {
assertEquals(OperationOutcome.class, resp.getEntry().get(0).getResource().getClass());
OperationOutcome outcome = (OperationOutcome) resp.getEntry().get(0).getResource();
assertThat(outcome.getIssue().get(1).getDetails(), containsString("Placeholder resource ID \"Patient/urn:oid:0.1.2.3\" was replaced with permanent ID \"Patient/"));
assertThat(outcome.getIssue().get(1).getDetails(), containsString("Placeholder resource ID \"urn:oid:0.1.2.3\" was replaced with permanent ID \"Patient/"));
assertTrue(resp.getEntry().get(1).getTransactionResponse().getLocation(), new IdDt(resp.getEntry().get(1).getTransactionResponse().getLocation()).getIdPart().matches("^[0-9]+$"));
assertTrue(resp.getEntry().get(2).getTransactionResponse().getLocation(), new IdDt(resp.getEntry().get(2).getTransactionResponse().getLocation()).getIdPart().matches("^[0-9]+$"));

View File

@ -82,6 +82,7 @@ import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.gclient.IQuery;
import ca.uhn.fhir.rest.gclient.IReadExecutable;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.rest.server.Constants;
@ -410,6 +411,34 @@ public class ResourceProviderDstu2Test {
}
@Test
public void testBundleCreate() throws Exception {
IGenericClient client = ourClient;
String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/document-father.json"));
IdDt id = client.create().resource(resBody).execute().getId();
ourLog.info("Created: {}", id);
ca.uhn.fhir.model.dstu2.resource.Bundle bundle = client.read().resource(ca.uhn.fhir.model.dstu2.resource.Bundle.class).withId(id).execute();
ourLog.info(ourFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
}
@Test
public void testBundleCreateWithTypeTransaction() throws Exception {
IGenericClient client = ourClient;
String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/document-father.json"));
resBody = resBody.replace("\"type\": \"document\"", "\"type\": \"transaction\"");
try {
client.create().resource(resBody).execute().getId();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value other than 'document' - Value was: transaction"));
}
}
/**
* See #147
*/

View File

@ -0,0 +1,475 @@
{
"resourceType": "Bundle",
"meta": {
"lastUpdated": "2013-05-28T22:12:21Z",
"tag": [
{
"system": "http://hl7.org/fhir/tag",
"code": "document"
}
]
},
"type": "document",
"base": "http://fhir.healthintersections.com.au/open",
"entry": [
{
"base": "urn:uuid:",
"resource": {
"resourceType": "Composition",
"id": "180f219f-97a8-486d-99d9-ed631fe4fc57",
"meta": {
"lastUpdated": "2013-05-28T22:12:21Z"
},
"text": {
"status": "generated",
"div": "<div><p><b>Generated Narrative with Details</b></p><p><b>id</b>: 180f219f-97a8-486d-99d9-ed631fe4fc57</p><p><b>meta</b>: </p><p><b>date</b>: Feb 1, 2013 12:30:02 PM</p><p><b>type</b>: Discharge Summary from Responsible Clinician <span>(Details : {LOINC code '28655-9' = 'Physician attending Discharge summary)</span></p><p><b>status</b>: final</p><p><b>confidentiality</b>: N</p><p><b>author</b>: <a>Doctor Dave. Generated Summary: 23; Adam Careful </a></p><p><b>encounter</b>: <a>http://fhir.healthintersections.com.au/open/Encounter/doc-example</a></p></div>"
},
"date": "2013-02-01T12:30:02Z",
"type": {
"coding": [
{
"system": "http://loinc.org",
"code": "28655-9"
}
],
"text": "Discharge Summary from Responsible Clinician"
},
"status": "final",
"confidentiality": "N",
"subject": {
"reference": "http://fhir.healthintersections.com.au/open/Patient/d1",
"display": "Eve Everywoman"
},
"author": [
{
"reference": "Practitioner/example",
"display": "Doctor Dave"
}
],
"encounter": {
"reference": "http://fhir.healthintersections.com.au/open/Encounter/doc-example"
},
"section": [
{
"title": "Reason for admission",
"content": {
"reference": "urn:uuid:d0dd51d3-3ab2-4c84-b697-a630c3e40e7a"
}
},
{
"title": "Medications on Discharge",
"content": {
"reference": "urn:uuid:673f8db5-0ffd-4395-9657-6da00420bbc1"
}
},
{
"title": "Known allergies",
"content": {
"reference": "urn:uuid:68f86194-e6e1-4f65-b64a-5314256f8d7b"
}
}
]
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": "example",
"meta": {
"lastUpdated": "2013-05-05T16:13:03Z"
},
"text": {
"status": "generated",
"div": "<div>\n \n <p>Dr Adam Careful is a Referring Practitioner for Acme Hospital from 1-Jan 2012 to 31-Mar\n 2012</p>\n \n </div>"
},
"identifier": [
{
"system": "http://www.acme.org/practitioners",
"value": "23"
}
],
"name": {
"family": [
"Careful"
],
"given": [
"Adam"
],
"prefix": [
"Dr"
]
},
"practitionerRole": [
{
"managingOrganization": {
"reference": "Organization/1"
},
"role": {
"coding": [
{
"system": "http://hl7.org/fhir/v2/0286",
"code": "RP"
}
]
},
"period": {
"start": "2012-01-01",
"end": "2012-03-31"
}
}
]
}
},
{
"resource": {
"resourceType": "Patient",
"id": "d1",
"text": {
"status": "generated",
"div": "<div>\n <h1>Eve Everywoman</h1>\n </div>"
},
"name": [
{
"text": "Eve Everywoman",
"family": [
"Everywoman1"
],
"given": [
"Eve"
]
}
],
"telecom": [
{
"system": "phone",
"value": "555-555-2003",
"use": "work"
}
],
"gender": "female",
"birthDate": "1955-01-06",
"address": [
{
"use": "home",
"line": [
"2222 Home Street"
]
}
],
"active": true
}
},
{
"resource": {
"resourceType": "Encounter",
"id": "doc-example",
"meta": {
"lastUpdated": "2013-05-05T16:13:03Z"
},
"text": {
"status": "generated",
"div": "<div> Admitted to Orthopedics Service,\n Middlemore Hospital between Jan 20 and Feb ist 2013 </div>"
},
"identifier": [
{
"value": "S100"
}
],
"status": "finished",
"class": "inpatient",
"type": [
{
"text": "Orthopedic Admission"
}
],
"patient": {
"reference": "Patient/d1"
},
"period": {
"start": "2013-01-20T12:30:02Z",
"end": "2013-02-01T12:30:02Z"
},
"hospitalization": {
"dischargeDisposition": {
"text": "Discharged to care of GP"
}
}
}
},
{
"base": "urn:uuid:",
"resource": {
"resourceType": "List",
"id": "d0dd51d3-3ab2-4c84-b697-a630c3e40e7a",
"meta": {
"lastUpdated": "2013-05-05T16:13:03Z"
},
"text": {
"status": "additional",
"div": "<div>\n <table>\n <thead>\n <tr>\n <td>Details</td>\n <td/>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Acute Asthmatic attack. Was wheezing\n for days prior to admission.</td>\n <td/>\n </tr>\n </tbody>\n </table>\n </div>"
},
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8646-2",
"display": "Hospital admission diagnosis"
}
]
},
"subject": {
"reference": "http://fhir.healthintersections.com.au/open/Patient/d1",
"display": "Peter Patient"
},
"status": "current",
"mode": "working",
"entry": [
{
"item": {
"reference": "urn:uuid:541a72a8-df75-4484-ac89-ac4923f03b81"
}
}
]
}
},
{
"base": "urn:uuid:",
"resource": {
"resourceType": "Observation",
"id": "541a72a8-df75-4484-ac89-ac4923f03b81",
"meta": {
"lastUpdated": "2013-05-05T16:13:03Z"
},
"text": {
"status": "additional",
"div": "<div> Acute Asthmatic attack. Was wheezing\n for days prior to admission. </div>"
},
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "46241-6"
}
],
"text": "Reason for admission"
},
"valueString": "Acute Asthmatic attack. Was wheezing for days prior to admission.",
"status": "final",
"reliability": "ok"
}
},
{
"base": "urn:uuid:",
"resource": {
"resourceType": "List",
"id": "673f8db5-0ffd-4395-9657-6da00420bbc1",
"meta": {
"lastUpdated": "2013-05-05T16:13:03Z"
},
"text": {
"status": "additional",
"div": "<div>\n <table>\n <thead>\n <tr>\n <td>Medication</td>\n <td>Last Change</td>\n <td>Last ChangeReason</td>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Theophylline 200mg BD after meals</td>\n <td>continued</td>\n </tr>\n <tr>\n <td>Ventolin Inhaler</td>\n <td>stopped</td>\n <td>Getting side effect of tremor</td>\n </tr>\n </tbody>\n </table>\n </div>"
},
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "10183-2",
"display": "Hospital discharge medications"
}
]
},
"subject": {
"reference": "http://fhir.healthintersections.com.au/open/Patient/d1",
"display": "Peter Patient"
},
"status": "current",
"mode": "working",
"entry": [
{
"flag": [
{
"coding": [
{
"system": "http://www.ithealthboard.health.nz/fhir/ValueSet/medicationStatus",
"code": "started"
}
]
}
],
"item": {
"reference": "urn:uuid:124a6916-5d84-4b8c-b250-10cefb8e6e86"
}
},
{
"flag": [
{
"coding": [
{
"system": "http://www.ithealthboard.health.nz/fhir/ValueSet/medicationStatus",
"code": "stopped"
}
]
}
],
"deleted": true,
"item": {
"reference": "MedicationPrescription/1",
"display": "use of Ventolin Inhaler was discontinued"
}
}
]
}
},
{
"base": "urn:uuid:",
"resource": {
"resourceType": "MedicationPrescription",
"id": "124a6916-5d84-4b8c-b250-10cefb8e6e86",
"meta": {
"lastUpdated": "2013-05-05T16:13:03Z"
},
"text": {
"status": "generated",
"div": "<div>\n <p>Theophylline 200mg twice a day</p>\n </div>"
},
"contained": [
{
"resourceType": "Medication",
"id": "med1",
"name": "Theophylline 200mg",
"code": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "66493003"
}
]
}
}
],
"patient": {
"reference": "http://fhir.healthintersections.com.au/open/Patient/d1",
"display": "Peter Patient"
},
"prescriber": {
"reference": "Practitioner/example",
"display": "Peter Practitioner"
},
"reasonCodeableConcept": {
"text": "Management of Asthma"
},
"medicationReference": {
"reference": "#med1",
"display": "Theophylline 200mg BD"
},
"dosageInstruction": [
{
"additionalInstructions": {
"text": "Take with Food"
},
"scheduledTiming": {
"repeat": {
"frequency": 2,
"period": 1,
"periodUnits": "d"
}
},
"route": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "394899003",
"display": "oral administration of treatment"
}
]
},
"doseQuantity": {
"value": 1,
"units": "tablet",
"system": "http://unitsofmeasure.org",
"code": "tbl"
}
}
]
}
},
{
"base": "urn:uuid:",
"resource": {
"resourceType": "List",
"id": "68f86194-e6e1-4f65-b64a-5314256f8d7b",
"meta": {
"lastUpdated": "2013-05-05T16:13:03Z"
},
"text": {
"status": "additional",
"div": "<div>\n <table>\n <thead>\n <tr>\n <td>Allergen</td>\n <td>Reaction</td>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Doxycycline</td>\n <td>Hives</td>\n </tr>\n </tbody>\n </table>\n </div>"
},
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "48765-2",
"display": "Allergies and adverse reactions Document"
}
]
},
"subject": {
"reference": "http://fhir.healthintersections.com.au/open/Patient/d1",
"display": "Peter Patient"
},
"status": "current",
"mode": "working",
"entry": [
{
"item": {
"reference": "urn:uuid:47600e0f-b6b5-4308-84b5-5dec157f7637"
}
}
]
}
},
{
"base": "urn:uuid:",
"resource": {
"resourceType": "AllergyIntolerance",
"id": "47600e0f-b6b5-4308-84b5-5dec157f7637",
"meta": {
"lastUpdated": "2013-05-05T16:13:03Z"
},
"text": {
"status": "generated",
"div": "<div>Sensitivity to Doxycycline :\n Hives</div>"
},
"recordedDate": "2012-09-17",
"patient": {
"reference": "http://fhir.healthintersections.com.au/open/Patient/d1",
"display": "Eve Everywoman"
},
"substance": {
"text": "Doxycycline"
},
"status": "confirmed",
"criticality": "high",
"type": "immune",
"event": [
{
"manifestation": [
{
"coding": [
{
"system": "http://example.org/system",
"code": "xxx",
"display": "Hives"
}
],
"text": "Hives"
}
]
}
]
}
}
]
}

View File

@ -34,6 +34,22 @@ public class IdDtTest {
assertFalse(id.isLocal());
}
@Test
public void testDetectLocalBase() {
assertEquals("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getValue());
assertEquals("urn:uuid:", new IdDt("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl());
assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart());
assertEquals("cid:180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getValue());
assertEquals("cid:", new IdDt("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl());
assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart());
assertEquals("#180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("#180f219f-97a8-486d-99d9-ed631fe4fc57").getValue());
assertEquals("#", new IdDt("#180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl());
assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdDt("#180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart());
}
/**
* See #67
*/
@ -41,34 +57,11 @@ public class IdDtTest {
public void testComplicatedLocal() {
IdDt id = new IdDt("#Patient/cid:Patient-72/_history/1");
assertTrue(id.isLocal());
assertNull(id.getBaseUrl());
assertEquals("#", id.getBaseUrl());
assertNull(id.getResourceType());
assertNull(id.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id.getIdPart());
assertEquals("Patient/cid:Patient-72/_history/1", id.getIdPart());
IdDt id2 = new IdDt("#Patient/cid:Patient-72/_history/1");
assertEquals(id, id2);
id2 = id2.toUnqualified();
assertTrue(id2.isLocal());
assertNull(id2.getBaseUrl());
assertNull(id2.getResourceType());
assertNull(id2.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart());
id2 = id2.toVersionless();
assertTrue(id2.isLocal());
assertNull(id2.getBaseUrl());
assertNull(id2.getResourceType());
assertNull(id2.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart());
id2 = id2.toUnqualifiedVersionless();
assertTrue(id2.isLocal());
assertNull(id2.getBaseUrl());
assertNull(id2.getResourceType());
assertNull(id2.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart());
}
@Test

View File

@ -99,7 +99,7 @@ public class JsonParserTest {
assertEquals(exp, act);
}
@Test
public void testDecimalPrecisionPreserved() {
String number = "52.3779939997090374535378485873776474764643249869328698436986235758587";

View File

@ -537,6 +537,8 @@ public class XmlParserTest {
// Re-parse the bundle
patient = (Patient) xmlParser.parseResource(xmlParser.encodeResourceToString(patient));
assertEquals("#1", patient.getManagingOrganization().getReference().getValue());
assertEquals("#", patient.getManagingOrganization().getReference().getBaseUrl());
assertEquals("1", patient.getManagingOrganization().getReference().getIdPart());
assertNotNull(patient.getManagingOrganization().getResource());
org = (Organization) patient.getManagingOrganization().getResource();

View File

@ -1,7 +1,13 @@
package ca.uhn.fhir.parser;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.util.ArrayList;
@ -14,7 +20,6 @@ import net.sf.json.JsonConfig;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains;
import org.junit.Assert;
import org.junit.Test;
@ -64,6 +69,87 @@ public class JsonParserDstu2Test {
assertThat(ourCtx.newJsonParser().setOmitResourceId(true).encodeResourceToString(p), not(containsString("123")));
}
@Test
public void testParseAndEncodeBundleWithUuidBase() {
//@formatter:off
String input =
"{\n" +
" \"resourceType\":\"Bundle\",\n" +
" \"type\":\"document\",\n" +
" \"entry\":[\n" +
" {\n" +
" \"base\":\"urn:uuid:\",\n" +
" \"resource\":{\n" +
" \"resourceType\":\"Composition\",\n" +
" \"id\":\"180f219f-97a8-486d-99d9-ed631fe4fc57\",\n" +
" \"meta\":{\n" +
" \"lastUpdated\":\"2013-05-28T22:12:21Z\"\n" +
" },\n" +
" \"text\":{\n" +
" \"status\":\"generated\",\n" +
" \"div\":\"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><p><b>Generated Narrative with Details</b></p><p><b>id</b>: 180f219f-97a8-486d-99d9-ed631fe4fc57</p><p><b>meta</b>: </p><p><b>date</b>: Feb 1, 2013 12:30:02 PM</p><p><b>type</b>: Discharge Summary from Responsible Clinician <span>(Details : {LOINC code '28655-9' = 'Physician attending Discharge summary)</span></p><p><b>status</b>: final</p><p><b>confidentiality</b>: N</p><p><b>author</b>: <a>Doctor Dave. Generated Summary: 23; Adam Careful </a></p><p><b>encounter</b>: <a>http://fhir.healthintersections.com.au/open/Encounter/doc-example</a></p></div>\"\n" +
" },\n" +
" \"date\":\"2013-02-01T12:30:02Z\",\n" +
" \"type\":{\n" +
" \"coding\":[\n" +
" {\n" +
" \"system\":\"http://loinc.org\",\n" +
" \"code\":\"28655-9\"\n" +
" }\n" +
" ],\n" +
" \"text\":\"Discharge Summary from Responsible Clinician\"\n" +
" },\n" +
" \"status\":\"final\",\n" +
" \"confidentiality\":\"N\",\n" +
" \"subject\":{\n" +
" \"reference\":\"http://fhir.healthintersections.com.au/open/Patient/d1\",\n" +
" \"display\":\"Eve Everywoman\"\n" +
" },\n" +
" \"author\":[\n" +
" {\n" +
" \"reference\":\"Practitioner/example\",\n" +
" \"display\":\"Doctor Dave\"\n" +
" }\n" +
" ],\n" +
" \"encounter\":{\n" +
" \"reference\":\"http://fhir.healthintersections.com.au/open/Encounter/doc-example\"\n" +
" },\n" +
" \"section\":[\n" +
" {\n" +
" \"title\":\"Reason for admission\",\n" +
" \"content\":{\n" +
" \"reference\":\"urn:uuid:d0dd51d3-3ab2-4c84-b697-a630c3e40e7a\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"title\":\"Medications on Discharge\",\n" +
" \"content\":{\n" +
" \"reference\":\"urn:uuid:673f8db5-0ffd-4395-9657-6da00420bbc1\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"title\":\"Known allergies\",\n" +
" \"content\":{\n" +
" \"reference\":\"urn:uuid:68f86194-e6e1-4f65-b64a-5314256f8d7b\"\n" +
" }\n" +
" }\n" +
" ]\n" +
" }\n" +
" }" +
" ]" +
"}";
//@formatter:on
ca.uhn.fhir.model.dstu2.resource.Bundle parsed = ourCtx.newJsonParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, input);
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed);
ourLog.info(encoded);
assertEquals("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57", parsed.getEntry().get(0).getResource().getId().getValue());
assertEquals("urn:uuid:", parsed.getEntry().get(0).getResource().getId().getBaseUrl());
assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", parsed.getEntry().get(0).getResource().getId().getIdPart());
assertThat(encoded, containsString("\"id\":\"180f219f-97a8-486d-99d9-ed631fe4fc57\""));
}
@Test

View File

@ -1108,7 +1108,15 @@ public class GenericClientDstu2Test {
assertNotNull(response.getOperationOutcome());
assertEquals("FOOBAR", response.getOperationOutcome().getIssueFirstRep().getDetailsElement().getValue());
idx++;
}
response = client.validate().resource(ourCtx.newJsonParser().encodeResourceToString(p)).prettyPrint().execute();
assertEquals("http://example.com/fhir/Patient/$validate?_format=json&_pretty=true", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
assertThat(extractBody(capt, idx), containsString("\"resourceType\":\"Parameters\",\n"));
assertNotNull(response.getOperationOutcome());
assertEquals("FOOBAR", response.getOperationOutcome().getIssueFirstRep().getDetailsElement().getValue());
idx++;
}
@BeforeClass
public static void beforeClass() {

View File

@ -29,7 +29,8 @@ package org.hl7.fhir.instance.model;
*/
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.math.BigDecimal;
@ -42,6 +43,8 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.parser.DataFormatException;
/**
* This class represents the logical identity for a resource, or as much of that
* identity is known. In FHIR, every resource must have a "logical ID" which
@ -311,6 +314,11 @@ public final class IdType extends UriType implements IPrimitiveType<String>, IId
public String getValue() {
String retVal = super.getValue();
if (retVal == null && myHaveComponentParts) {
if (determineLocalPrefix(myBaseUrl) != null && myResourceType == null && myUnqualifiedVersionId == null) {
return myBaseUrl + myUnqualifiedId;
}
StringBuilder b = new StringBuilder();
if (isNotBlank(myBaseUrl)) {
b.append(myBaseUrl);
@ -428,9 +436,37 @@ public final class IdType extends UriType implements IPrimitiveType<String>, IId
*/
@Override
public boolean isLocal() {
return myUnqualifiedId != null && myUnqualifiedId.isEmpty() == false && myUnqualifiedId.charAt(0) == '#';
return "#".equals(myBaseUrl);
}
private String determineLocalPrefix(String theValue) {
if (theValue == null || theValue.isEmpty()) {
return null;
}
if (theValue.startsWith("#")) {
return "#";
}
int lastPrefix = -1;
for (int i = 0; i < theValue.length(); i++) {
char nextChar = theValue.charAt(i);
if (nextChar == ':') {
lastPrefix = i;
} else if (!Character.isLetter(nextChar) || !Character.isLowerCase(nextChar)) {
break;
}
}
if (lastPrefix != -1) {
String candidate = theValue.substring(0, lastPrefix + 1);
if (candidate.startsWith("cid:") || candidate.startsWith("urn:")) {
return candidate;
} else {
return null;
}
} else {
return null;
}
}
/**
* Set the value
*
@ -443,22 +479,29 @@ public final class IdType extends UriType implements IPrimitiveType<String>, IId
* </p>
*/
@Override
public IdType setValue(String theValue) {
public IdType setValue(String theValue) throws DataFormatException {
// TODO: add validation
super.setValue(theValue);
myHaveComponentParts = false;
String localPrefix = determineLocalPrefix(theValue);
if (StringUtils.isBlank(theValue)) {
myBaseUrl = null;
super.setValue(null);
myUnqualifiedId = null;
myUnqualifiedVersionId = null;
myResourceType = null;
} else if (theValue.charAt(0) == '#') {
} else if (theValue.charAt(0) == '#' && theValue.length() > 1) {
super.setValue(theValue);
myUnqualifiedId = theValue;
myBaseUrl = "#";
myUnqualifiedId = theValue.substring(1);
myUnqualifiedVersionId = null;
myResourceType = null;
myHaveComponentParts = true;
} else if (localPrefix != null) {
myBaseUrl = localPrefix;
myUnqualifiedId = theValue.substring(localPrefix.length());
} else {
int vidIndex = theValue.indexOf("/_history/");
int idIndex;

View File

@ -0,0 +1,226 @@
package ca.uhn.fhir.model;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import org.hl7.fhir.instance.model.IdType;
import org.hl7.fhir.instance.model.Patient;
import org.hl7.fhir.instance.model.Reference;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
public class IdTypeTest {
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(IdTypeTest.class);
@Test
public void testDetectLocal() {
IdType id;
id = new IdType("#123");
assertEquals("#123", id.getValue());
assertTrue(id.isLocal());
id = new IdType("#Medication/499059CE-CDD4-48BC-9014-528A35D15CED/_history/1");
assertEquals("#Medication/499059CE-CDD4-48BC-9014-528A35D15CED/_history/1", id.getValue());
assertTrue(id.isLocal());
id = new IdType("http://example.com/Patient/33#123");
assertEquals("http://example.com/Patient/33#123", id.getValue());
assertFalse(id.isLocal());
}
@Test
public void testDetectLocalBase() {
assertEquals("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getValue());
assertEquals("urn:uuid:", new IdType("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl());
assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("urn:uuid:180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart());
assertEquals("cid:180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getValue());
assertEquals("cid:", new IdType("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl());
assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("cid:180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart());
assertEquals("#180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getValue());
assertEquals("#", new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl());
assertEquals("180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart());
}
/**
* See #67
*/
@Test
public void testComplicatedLocal() {
IdType id = new IdType("#Patient/cid:Patient-72/_history/1");
assertTrue(id.isLocal());
assertEquals("#", id.getBaseUrl());
assertNull(id.getResourceType());
assertNull(id.getVersionIdPart());
assertEquals("Patient/cid:Patient-72/_history/1", id.getIdPart());
IdType id2 = new IdType("#Patient/cid:Patient-72/_history/1");
assertEquals(id, id2);
id2 = id2.toUnqualified();
assertFalse(id2.isLocal());
assertNull(id2.getBaseUrl());
assertNull(id2.getResourceType());
assertNull(id2.getVersionIdPart());
assertEquals("Patient/cid:Patient-72/_history/1", id2.getIdPart());
}
@Test
public void testDetermineBase() {
IdType rr;
rr = new IdType("http://foo/fhir/Organization/123");
assertEquals("http://foo/fhir", rr.getBaseUrl());
rr = new IdType("http://foo/fhir/Organization/123/_history/123");
assertEquals("http://foo/fhir", rr.getBaseUrl());
rr = new IdType("Organization/123/_history/123");
assertEquals(null, rr.getBaseUrl());
}
@Test
public void testParseValueAbsolute() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("http://foo/fhir/Organization/123");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals("Organization", ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
}
@Test
public void testBigDecimalIds() {
IdType id = new IdType(new BigDecimal("123"));
assertEquals(id.getIdPartAsBigDecimal(), new BigDecimal("123"));
}
@Test
public void testParseValueAbsoluteWithVersion() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("http://foo/fhir/Organization/123/_history/999");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals("Organization", ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
assertEquals(null, ref.getReferenceElement().getVersionIdPart());
}
@Test
public void testViewMethods() {
IdType i = new IdType("http://foo/fhir/Organization/123/_history/999");
assertEquals("Organization/123/_history/999", i.toUnqualified().getValue());
assertEquals("http://foo/fhir/Organization/123", i.toVersionless().getValue());
assertEquals("Organization/123", i.toUnqualifiedVersionless().getValue());
}
@Test
public void testParseValueWithVersion() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("/123/_history/999");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals(null, ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
assertEquals(null, ref.getReferenceElement().getVersionIdPart());
}
@Test
public void testParseValueMissingType1() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("/123");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals(null, ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
}
@Test
public void testParseValueMissingType2() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("123");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals(null, ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
}
@Test
public void testParseValueRelative1() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("Organization/123");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals("Organization", ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
}
@Test
public void testParseValueRelative2() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("/Organization/123");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals("Organization", ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
}
private Patient parseAndEncode(Patient patient) {
String encoded = ourCtx.newXmlParser().encodeResourceToString(patient);
ourLog.info("\n" + encoded);
return ourCtx.newXmlParser().parseResource(Patient.class, encoded);
}
@BeforeClass
public static void beforeClass() {
ourCtx = new FhirContext();
}
}

View File

@ -597,6 +597,7 @@ public class Controller {
CaptureInterceptor interceptor = new CaptureInterceptor();
GenericClient client = theRequest.newClient(theReq, getContext(theRequest), myConfig, interceptor);
client.setPrettyPrint(true);
Class<? extends IResource> type = null; // def.getImplementingClass();
if ("history-type".equals(theMethod)) {
@ -616,8 +617,10 @@ public class Controller {
try {
if (body.startsWith("{")) {
resource = getContext(theRequest).newJsonParser().parseResource(type, body);
client.setEncoding(EncodingEnum.JSON);
} else if (body.startsWith("<")) {
resource = getContext(theRequest).newXmlParser().parseResource(type, body);
client.setEncoding(EncodingEnum.XML);
} else {
theModel.put("errorMsg", "Message body does not appear to be a valid FHIR resource instance document. Body should start with '<' (for XML encoding) or '{' (for JSON encoding).");
return;
@ -637,7 +640,7 @@ public class Controller {
try {
if (validate) {
outcomeDescription = "Validate Resource";
client.validate(resource);
client.validate().resource(resource).prettyPrint().execute();
} else {
String id = theReq.getParameter("resource-create-id");
if ("update".equals(theMethod)) {

View File

@ -92,9 +92,6 @@ public class TinderJpaRestServerMojo extends AbstractMojo {
}
for (String next : keys) {
if (next.startsWith("resource.")) {
if (next.endsWith(".Bundle")) {
continue;
}
baseResourceNames.add(next.substring("resource.".length()).toLowerCase());
}
}

View File

@ -7,7 +7,8 @@ import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.jpa.provider.JpaResourceProvider${versionCapitalized};
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.*;
import ca.uhn.fhir.model.${version}.composite.*;
import ca.uhn.fhir.model.${version}.resource.*;
@ -16,7 +17,7 @@ import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.model.dstu.resource.Binary;
// import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.api.Bundle;
// import ca.uhn.fhir.model.api.Bundle;
public class ${className}ResourceProvider extends JpaResourceProvider${versionCapitalized}<${className}> {

View File

@ -27,7 +27,12 @@
</util:list>
#foreach ( $res in $resources )
<bean id="my${res.name}Dao${versionCapitalized}" class="ca.uhn.fhir.jpa.dao.FhirResourceDao${versionCapitalized}">
<bean id="my${res.name}Dao${versionCapitalized}"
#if ( ${res.name} == 'Bundle' )
class="ca.uhn.fhir.jpa.dao.Fhir${res.name}ResourceDao${versionCapitalized}">
#else
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${versionCapitalized}">
#end
<property name="resourceType" value="ca.uhn.fhir.model.${version}.resource.${res.declaringClassNameComplete}"/>
<property name="context" ref="myFhirContext${versionCapitalized}"/>
</bean>

View File

@ -14,7 +14,7 @@
<groupId>ca.uhn.hapi.example</groupId>
<artifactId>restful-server-example</artifactId>
<version>1.0</version>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>HAPI FHIR Sample RESTful Server</name>