diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResourceReference.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResourceReference.java index dcb81b5ea54..8295ea113f8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResourceReference.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResourceReference.java @@ -30,6 +30,7 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.client.BaseClient; @@ -49,6 +50,14 @@ public abstract class BaseResourceReference extends BaseElement { // nothing } + /** + * Constructor + */ + public BaseResourceReference(Class theResourceType, IdDt theResourceId) { + myResourceType = theResourceType; + myResourceId = theResourceId.getValue(); + } + /** * Constructor */ @@ -57,11 +66,6 @@ public abstract class BaseResourceReference extends BaseElement { myResourceId = theResourceId; } - @Override - protected boolean isBaseEmpty() { - return super.isBaseEmpty() && myResource == null && myResourceType == null && StringUtils.isBlank(myResourceId); - } - public BaseResourceReference(IResource theResource) { myResource=theResource; } @@ -102,6 +106,11 @@ public abstract class BaseResourceReference extends BaseElement { return getReference().getValue(); } + @Override + protected boolean isBaseEmpty() { + return super.isBaseEmpty() && myResource == null && myResourceType == null && StringUtils.isBlank(myResourceId); + } + /** * Returns the referenced resource, fetching it if it has not already been loaded. This method invokes the HTTP client to retrieve the resource unless it has already been loaded, or was a * contained resource in which case it is simply returned. diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java index 360e5625ac2..5d9d129bc5a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java @@ -45,6 +45,7 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.Description; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; /** @@ -81,6 +82,16 @@ public class ResourceReferenceDt public ResourceReferenceDt(Class theResourceType, String theResourceId) { super(theResourceType, theResourceId); } + + /** + * Constructor which creates a normal resource reference + * + * @param theResourceType The resource type + * @param theResourceId The resource ID + */ + public ResourceReferenceDt(Class theResourceType, IdDt theResourceId) { + super(theResourceType, theResourceId); + } /** * Constructor which creates a resource reference containing the actual diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java index 1f8b9ab86ce..9934df7ddf4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java @@ -1082,6 +1082,9 @@ public class Observation extends BaseResource implements IResource { *

*/ public java.util.List getPerformer() { + if (myPerformer == null) { + myPerformer = new java.util.ArrayList(); + } return myPerformer; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java new file mode 100644 index 00000000000..be59c28b368 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java @@ -0,0 +1,19 @@ +package ca.uhn.fhir.rest.param; + +import ca.uhn.fhir.model.api.IQueryParameterType; + +public class ReferenceParam implements IQueryParameterType { + + private String myValue; + + @Override + public void setValueAsQueryToken(String theParameter) { + myValue=theParameter; + } + + @Override + public String getValueAsQueryToken() { + return myValue; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/InvalidRequestException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/InvalidRequestException.java index 7cc7030c429..ad49c4e0ba2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/InvalidRequestException.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/InvalidRequestException.java @@ -25,7 +25,7 @@ import ca.uhn.fhir.rest.server.Constants; /** * Represents an HTTP 400 Bad Request response. * This status indicates that the client's message was invalid (e.g. not a valid FHIR Resource - * per the specifications), as opposed to the {@link InvalidRequestException} which indicates + * per the specifications), as opposed to the {@link UnprocessableEntityException} which indicates * that data does not pass business rule validation on the server. * *

diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java index 9a4d34fbf8c..c6d171a63d2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java @@ -21,6 +21,7 @@ import javax.persistence.criteria.Expression; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; import org.springframework.transaction.PlatformTransactionManager; @@ -42,6 +43,7 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.entity.ResourceLink; import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -54,6 +56,7 @@ import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.composite.QuantityDt; +import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.primitive.BaseDateTimeDt; import ca.uhn.fhir.model.primitive.IdDt; @@ -62,8 +65,10 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.QualifiedDateParam; +import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.util.FhirTerser; @@ -76,205 +81,108 @@ public class FhirResourceDao @Autowired private PlatformTransactionManager myPlatformTransactionManager; + @Autowired + private List> myResourceDaos; + private String myResourceName; private Class myResourceType; private Class myTableType; - private String myResourceName; + private Map, Class>> myResourceTypeToDao; - @Transactional(propagation = Propagation.SUPPORTS) - @Override - public MethodOutcome create(T theResource) { + private Set addPredicateDate(Set thePids, List theOrParams) { + if (theOrParams == null || theOrParams.isEmpty()) { + return thePids; + } - final X entity = toEntity(theResource); + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(Long.class); + Root from = cq.from(ResourceIndexedSearchParamDate.class); + cq.select(from.get("myResourcePid").as(Long.class)); - entity.setPublished(new Date()); - entity.setUpdated(entity.getPublished()); + List codePredicates = new ArrayList(); + for (IQueryParameterType nextOr : theOrParams) { + IQueryParameterType params = nextOr; - final List stringParams = extractSearchParamStrings(entity, theResource); - final List tokenParams = extractSearchParamTokens(entity, theResource); - final List numberParams = extractSearchParamNumber(entity, theResource); - final List dateParams = extractSearchParamDates(entity, theResource); - - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - template.execute(new TransactionCallback() { - @Override - public X doInTransaction(TransactionStatus theStatus) { - myEntityManager.persist(entity); - for (ResourceIndexedSearchParamString next : stringParams) { - myEntityManager.persist(next); - } - for (ResourceIndexedSearchParamToken next : tokenParams) { - myEntityManager.persist(next); - } - for (ResourceIndexedSearchParamNumber next : numberParams) { - myEntityManager.persist(next); - } - for (ResourceIndexedSearchParamDate next : dateParams) { - myEntityManager.persist(next); - } - return entity; + if (params instanceof QualifiedDateParam) { + QualifiedDateParam id = (QualifiedDateParam) params; + DateRangeParam range = new DateRangeParam(id); + addPredicateDateFromRange(builder, from, codePredicates, range); + } else if (params instanceof DateRangeParam) { + DateRangeParam range = (DateRangeParam) params; + addPredicateDateFromRange(builder, from, codePredicates, range); + } else { + throw new IllegalArgumentException("Invalid token type: " + params.getClass()); } - }); - MethodOutcome outcome = toMethodOutcome(entity); - return outcome; - } - - @Transactional(propagation = Propagation.REQUIRED) - @Override - public List history(IdDt theId) { - ArrayList retVal = new ArrayList(); - - String resourceType = myCtx.getResourceDefinition(myResourceType).getName(); - TypedQuery q = myEntityManager.createQuery(ResourceHistoryTable.Q_GETALL, ResourceHistoryTable.class); - q.setParameter("PID", theId.asLong()); - q.setParameter("RESTYPE", resourceType); - - // TypedQuery query = - // myEntityManager.createQuery(criteriaQuery); - List results = q.getResultList(); - for (ResourceHistoryTable next : results) { - retVal.add(toResource(next)); } - try { - retVal.add(read(theId)); - } catch (ResourceNotFoundException e) { - // ignore + Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); + + Predicate type = builder.equal(from.get("myResourceType"), myResourceName); + if (thePids.size() > 0) { + Predicate inPids = (from.get("myResourcePid").in(thePids)); + cq.where(builder.and(type, inPids, masterCodePredicate)); + } else { + cq.where(builder.and(type, masterCodePredicate)); } - if (retVal.isEmpty()) { - throw new ResourceNotFoundException(theId); + TypedQuery q = myEntityManager.createQuery(cq); + return new HashSet(q.getResultList()); + } + + private Set addPredicateReference(Set thePids, List theOrParams) { + if (theOrParams == null || theOrParams.isEmpty()) { + return thePids; } - return retVal; - } + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(Long.class); + Root from = cq.from(ResourceIndexedSearchParamDate.class); + cq.select(from.get("myResourcePid").as(Long.class)); - @PostConstruct - public void postConstruct() throws Exception { - myResourceType = myTableType.newInstance().getResourceType(); - myCtx = new FhirContext(myResourceType); - myResourceName = myCtx.getResourceDefinition(myResourceType).getName(); - } + List codePredicates = new ArrayList(); + for (IQueryParameterType nextOr : theOrParams) { + IQueryParameterType params = nextOr; - @Transactional(propagation = Propagation.REQUIRED) - @Override - public T read(IdDt theId) { - X entity = readEntity(theId); - - T retVal = toResource(entity); - return retVal; - } - - @Override - public List search(Map theParams) { - Map>> map = new HashMap>>(); - for (Entry nextEntry : theParams.entrySet()) { - map.put(nextEntry.getKey(), new ArrayList>()); - map.get(nextEntry.getKey()).add(Collections.singletonList(nextEntry.getValue())); - } - return searchWithAndOr(map); - } - - @Override - public List search(String theSpName, IQueryParameterType theValue) { - return search(Collections.singletonMap(theSpName, theValue)); - } - - @Override - public List searchWithAndOr(Map>> theParams) { - Map>> params = theParams; - if (params == null) { - params = Collections.emptyMap(); - } - - RuntimeResourceDefinition resourceDef = myCtx.getResourceDefinition(myResourceType); - - Set pids = new HashSet(); - - for (Entry>> nextParamEntry : params.entrySet()) { - String nextParamName = nextParamEntry.getKey(); - RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); - if (nextParamDef != null) { - if (nextParamDef.getParamType() == SearchParamTypeEnum.TOKEN) { - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateToken(pids, nextAnd); - if (pids.isEmpty()) { - return new ArrayList(); - } - } - } else if (nextParamDef.getParamType() == SearchParamTypeEnum.STRING) { - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateString(pids, nextAnd); - if (pids.isEmpty()) { - return new ArrayList(); - } - } - } else if (nextParamDef.getParamType() == SearchParamTypeEnum.QUANTITY) { - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateQuantity(pids, nextAnd); - if (pids.isEmpty()) { - return new ArrayList(); - } - } - } else if (nextParamDef.getParamType() == SearchParamTypeEnum.DATE) { - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateDate(pids, nextAnd); - if (pids.isEmpty()) { - return new ArrayList(); - } - } - } else { - throw new IllegalArgumentException("Don't know how to handle parameter of type: " + nextParamDef.getParamType()); - } + if (params instanceof ReferenceParam) { + ReferenceParam id = (ReferenceParam) params; + } else { + throw new IllegalArgumentException("Invalid token type: " + params.getClass()); } + } - // Execute the query and make sure we return distinct results - { - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createQuery(myTableType); - Root from = cq.from(myTableType); - if (!params.isEmpty()) { - cq.where(from.get("myId").in(pids)); - } - TypedQuery q = myEntityManager.createQuery(cq); + Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); - List retVal = new ArrayList<>(); - for (X next : q.getResultList()) { - T resource = toResource(next); - retVal.add(resource); - } - return retVal; + Predicate type = builder.equal(from.get("myResourceType"), myResourceName); + if (thePids.size() > 0) { + Predicate inPids = (from.get("myResourcePid").in(thePids)); + cq.where(builder.and(type, inPids, masterCodePredicate)); + } else { + cq.where(builder.and(type, masterCodePredicate)); } + + TypedQuery q = myEntityManager.createQuery(cq); + return new HashSet(q.getResultList()); } - @Required - public void setTableType(Class theTableType) { - myTableType = theTableType; + private void addPredicateDateFromRange(CriteriaBuilder builder, Root from, List codePredicates, DateRangeParam range) { + Predicate singleCode; + Date lowerBound = range.getLowerBoundAsInstant(); + Date upperBound = range.getUpperBoundAsInstant(); + + if (lowerBound != null && upperBound != null) { + Predicate low = builder.greaterThanOrEqualTo(from. get("myValueLow"), lowerBound); + Predicate high = builder.lessThanOrEqualTo(from. get("myValueHigh"), upperBound); + singleCode = builder.and(low, high); + } else if (lowerBound != null) { + singleCode = builder.greaterThanOrEqualTo(from. get("myValueLow"), lowerBound); + } else { + singleCode = builder.lessThanOrEqualTo(from. get("myValueHigh"), upperBound); + } + + codePredicates.add(singleCode); } - - @Transactional(propagation = Propagation.SUPPORTS) - @Override - public MethodOutcome update(final T theResource, final IdDt theId) { - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - X savedEntity = template.execute(new TransactionCallback() { - @Override - public X doInTransaction(TransactionStatus theStatus) { - final X entity = readEntity(theId); - final ResourceHistoryTable existing = entity.toHistory(myCtx); - - populateResourceIntoEntity(theResource, entity); - myEntityManager.persist(existing); - - entity.setUpdated(new Date()); - myEntityManager.persist(entity); - return entity; - } - }); - - return toMethodOutcome(savedEntity); - } - + private Set addPredicateQuantity(Set thePids, List theOrParams) { if (theOrParams == null || theOrParams.isEmpty()) { return thePids; @@ -359,65 +267,6 @@ public class FhirResourceDao return new HashSet(q.getResultList()); } - private Set addPredicateDate(Set thePids, List theOrParams) { - if (theOrParams == null || theOrParams.isEmpty()) { - return thePids; - } - - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createQuery(Long.class); - Root from = cq.from(ResourceIndexedSearchParamDate.class); - cq.select(from.get("myResourcePid").as(Long.class)); - - List codePredicates = new ArrayList(); - for (IQueryParameterType nextOr : theOrParams) { - IQueryParameterType params = nextOr; - - if (params instanceof QualifiedDateParam) { - QualifiedDateParam id = (QualifiedDateParam) params; - DateRangeParam range = new DateRangeParam(id); - addPredicateDateFromRange(builder, from, codePredicates, range); - } else if (params instanceof DateRangeParam) { - DateRangeParam range = (DateRangeParam) params; - addPredicateDateFromRange(builder, from, codePredicates, range); - } else { - throw new IllegalArgumentException("Invalid token type: " + params.getClass()); - } - - } - - Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); - - Predicate type = builder.equal(from.get("myResourceType"), myResourceName); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myResourcePid").in(thePids)); - cq.where(builder.and(type, inPids, masterCodePredicate)); - } else { - cq.where(builder.and(type, masterCodePredicate)); - } - - TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); - } - - private void addPredicateDateFromRange(CriteriaBuilder builder, Root from, List codePredicates, DateRangeParam range) { - Predicate singleCode; - Date lowerBound = range.getLowerBoundAsInstant(); - Date upperBound = range.getUpperBoundAsInstant(); - - if (lowerBound != null && upperBound != null) { - Predicate low = builder.greaterThanOrEqualTo(from. get("myValueLow"), lowerBound); - Predicate high = builder.lessThanOrEqualTo(from. get("myValueHigh"), upperBound); - singleCode = builder.and(low, high); - } else if (lowerBound != null) { - singleCode = builder.greaterThanOrEqualTo(from. get("myValueLow"), lowerBound); - } else { - singleCode = builder.lessThanOrEqualTo(from. get("myValueHigh"), upperBound); - } - - codePredicates.add(singleCode); - } - private Set addPredicateString(Set thePids, List theOrParams) { if (theOrParams == null || theOrParams.isEmpty()) { return thePids; @@ -511,6 +360,203 @@ public class FhirResourceDao return new HashSet(q.getResultList()); } + @Transactional(propagation = Propagation.REQUIRED, readOnly=true) + @Override + public MethodOutcome create(T theResource) { + + final X entity = toEntity(theResource); + + entity.setPublished(new Date()); + entity.setUpdated(entity.getPublished()); + + final List stringParams = extractSearchParamStrings(entity, theResource); + final List tokenParams = extractSearchParamTokens(entity, theResource); + final List numberParams = extractSearchParamNumber(entity, theResource); + final List dateParams = extractSearchParamDates(entity, theResource); + final List links = extractResourceLinks(entity, theResource); + + TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); + template.setReadOnly(false); + template.execute(new TransactionCallback() { + @Override + public X doInTransaction(TransactionStatus theStatus) { + myEntityManager.persist(entity); + for (ResourceIndexedSearchParamString next : stringParams) { + myEntityManager.persist(next); + } + for (ResourceIndexedSearchParamToken next : tokenParams) { + myEntityManager.persist(next); + } + for (ResourceIndexedSearchParamNumber next : numberParams) { + myEntityManager.persist(next); + } + for (ResourceIndexedSearchParamDate next : dateParams) { + myEntityManager.persist(next); + } + for (ResourceLink next : links) { + myEntityManager.persist(next); + } + return entity; + } + }); + + MethodOutcome outcome = toMethodOutcome(entity); + return outcome; + } + + private List extractResourceLinks(X theEntity, T theResource) { + ArrayList retVal = new ArrayList(); + + RuntimeResourceDefinition def = myCtx.getResourceDefinition(theResource); + FhirTerser t = myCtx.newTerser(); + for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + if (nextSpDef.getParamType() != SearchParamTypeEnum.REFERENCE) { + continue; + } + + String nextPath = nextSpDef.getPath(); + + boolean multiType = false; + if (nextPath.endsWith("[x]")) { + multiType = true; + } + + List values = t.getValues(theResource, nextPath); + for (Object nextObject : values) { + ResourceLink nextEntity; + if (nextObject instanceof ResourceReferenceDt) { + ResourceReferenceDt nextValue = (ResourceReferenceDt) nextObject; + if (nextValue.isEmpty()) { + continue; + } + + Class type = nextValue.getResourceType(); + String id = nextValue.getResourceId(); + if (StringUtils.isBlank(id)) { + continue; + } + + if (myResourceTypeToDao == null) { + myResourceTypeToDao=new HashMap<>(); + for (IFhirResourceDao next : myResourceDaos) { + myResourceTypeToDao.put(next.getResourceType(), next.getTableType()); + } + } + + Class> tableType = myResourceTypeToDao.get(type); + BaseResourceTable target = myEntityManager.find(tableType, Long.valueOf(id)); + + nextEntity = new ResourceLink(nextPath, theEntity, target); + } else { + if (!multiType) { + throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass()); + } else { + continue; + } + } + if (nextEntity != null) { + retVal.add(nextEntity); + } + } + } + + return retVal; + } + + @Override + public Class getTableType() { + return myTableType; + } + + private List extractSearchParamDates(X theEntity, T theResource) { + ArrayList retVal = new ArrayList(); + + RuntimeResourceDefinition def = myCtx.getResourceDefinition(theResource); + FhirTerser t = myCtx.newTerser(); + for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + if (nextSpDef.getParamType() != SearchParamTypeEnum.DATE) { + continue; + } + + String nextPath = nextSpDef.getPath(); + + boolean multiType = false; + if (nextPath.endsWith("[x]")) { + multiType = true; + } + + List values = t.getValues(theResource, nextPath); + for (Object nextObject : values) { + if (nextObject == null) { + continue; + } + + ResourceIndexedSearchParamDate nextEntity; + if (nextObject instanceof BaseDateTimeDt) { + BaseDateTimeDt nextValue = (BaseDateTimeDt) nextObject; + if (nextValue.isEmpty()) { + continue; + } + nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue()); + } else { + if (!multiType) { + throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass()); + } else { + continue; + } + } + if (nextEntity != null) { + nextEntity.setResource(theEntity, def.getName()); + retVal.add(nextEntity); + } + } + } + + return retVal; + } + + private ArrayList extractSearchParamNumber(X theEntity, T theResource) { + ArrayList retVal = new ArrayList(); + + RuntimeResourceDefinition def = myCtx.getResourceDefinition(theResource); + FhirTerser t = myCtx.newTerser(); + for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + if (nextSpDef.getParamType() != SearchParamTypeEnum.NUMBER && nextSpDef.getParamType() != SearchParamTypeEnum.QUANTITY) { + continue; + } + + String nextPath = nextSpDef.getPath(); + List values = t.getValues(theResource, nextPath); + for (Object nextObject : values) { + if (nextObject == null || ((IDatatype) nextObject).isEmpty()) { + continue; + } + + String resourceName = nextSpDef.getName(); + boolean multiType = false; + if (nextPath.endsWith("[x]")) { + multiType = true; + } + + if (nextObject instanceof QuantityDt) { + QuantityDt nextValue = (QuantityDt) nextObject; + ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue().getValue(), nextValue.getSystem().getValueAsString(), nextValue.getUnits().getValue()); + nextEntity.setResource(theEntity, def.getName()); + retVal.add(nextEntity); + } else { + if (!multiType) { + throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass()); + } else { + continue; + } + } + } + } + + return retVal; + } + private List extractSearchParamStrings(X theEntity, T theResource) { ArrayList retVal = new ArrayList(); @@ -528,7 +574,7 @@ public class FhirResourceDao String nextPath = nextSpDef.getPath(); List values = t.getValues(theResource, nextPath); for (Object nextObject : values) { - if (((IDatatype) nextObject).isEmpty()) { + if (nextObject == null || ((IDatatype) nextObject).isEmpty()) { continue; } @@ -563,47 +609,6 @@ public class FhirResourceDao return retVal; } - private ArrayList extractSearchParamNumber(X theEntity, T theResource) { - ArrayList retVal = new ArrayList(); - - RuntimeResourceDefinition def = myCtx.getResourceDefinition(theResource); - FhirTerser t = myCtx.newTerser(); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { - if (nextSpDef.getParamType() != SearchParamTypeEnum.NUMBER && nextSpDef.getParamType() != SearchParamTypeEnum.QUANTITY) { - continue; - } - - String nextPath = nextSpDef.getPath(); - List values = t.getValues(theResource, nextPath); - for (Object nextObject : values) { - if (((IDatatype) nextObject).isEmpty()) { - continue; - } - - String resourceName = nextSpDef.getName(); - boolean multiType = false; - if (nextPath.endsWith("[x]")) { - multiType = true; - } - - if (nextObject instanceof QuantityDt) { - QuantityDt nextValue = (QuantityDt) nextObject; - ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue().getValue(), nextValue.getSystem().getValueAsString(), nextValue.getUnits().getValue()); - nextEntity.setResource(theEntity, def.getName()); - retVal.add(nextEntity); - } else { - if (!multiType) { - throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass()); - } else { - continue; - } - } - } - } - - return retVal; - } - private List extractSearchParamTokens(X theEntity, T theResource) { ArrayList retVal = new ArrayList(); @@ -664,44 +669,31 @@ public class FhirResourceDao return retVal; } - private List extractSearchParamDates(X theEntity, T theResource) { - ArrayList retVal = new ArrayList(); + @Transactional(propagation = Propagation.REQUIRED) + @Override + public List history(IdDt theId) { + ArrayList retVal = new ArrayList(); - RuntimeResourceDefinition def = myCtx.getResourceDefinition(theResource); - FhirTerser t = myCtx.newTerser(); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { - if (nextSpDef.getParamType() != SearchParamTypeEnum.DATE) { - continue; - } + String resourceType = myCtx.getResourceDefinition(myResourceType).getName(); + TypedQuery q = myEntityManager.createQuery(ResourceHistoryTable.Q_GETALL, ResourceHistoryTable.class); + q.setParameter("PID", theId.asLong()); + q.setParameter("RESTYPE", resourceType); - String nextPath = nextSpDef.getPath(); + // TypedQuery query = + // myEntityManager.createQuery(criteriaQuery); + List results = q.getResultList(); + for (ResourceHistoryTable next : results) { + retVal.add(toResource(next)); + } - boolean multiType = false; - if (nextPath.endsWith("[x]")) { - multiType = true; - } + try { + retVal.add(read(theId)); + } catch (ResourceNotFoundException e) { + // ignore + } - List values = t.getValues(theResource, nextPath); - for (Object nextObject : values) { - ResourceIndexedSearchParamDate nextEntity; - if (nextObject instanceof BaseDateTimeDt) { - BaseDateTimeDt nextValue = (BaseDateTimeDt) nextObject; - if (nextValue.isEmpty()) { - continue; - } - nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue()); - } else { - if (!multiType) { - throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass()); - } else { - continue; - } - } - if (nextEntity != null) { - nextEntity.setResource(theEntity, def.getName()); - retVal.add(nextEntity); - } - } + if (retVal.isEmpty()) { + throw new ResourceNotFoundException(theId); } return retVal; @@ -720,6 +712,26 @@ public class FhirResourceDao } + @PostConstruct + public void postConstruct() throws Exception { + myResourceType = myTableType.newInstance().getResourceType(); + myCtx = new FhirContext(myResourceType); + myResourceName = myCtx.getResourceDefinition(myResourceType).getName(); + } + + public Class getResourceType() { + return myResourceType; + } + + @Transactional(propagation = Propagation.REQUIRED) + @Override + public T read(IdDt theId) { + X entity = readEntity(theId); + + T retVal = toResource(entity); + return retVal; + } + private X readEntity(IdDt theId) { X entity = (X) myEntityManager.find(myTableType, theId.asLong()); if (entity == null) { @@ -728,6 +740,101 @@ public class FhirResourceDao return entity; } + @Override + public List search(Map theParams) { + Map>> map = new HashMap>>(); + for (Entry nextEntry : theParams.entrySet()) { + map.put(nextEntry.getKey(), new ArrayList>()); + map.get(nextEntry.getKey()).add(Collections.singletonList(nextEntry.getValue())); + } + return searchWithAndOr(map); + } + + @Override + public List search(String theSpName, IQueryParameterType theValue) { + return search(Collections.singletonMap(theSpName, theValue)); + } + + @Override + public List searchWithAndOr(Map>> theParams) { + Map>> params = theParams; + if (params == null) { + params = Collections.emptyMap(); + } + + RuntimeResourceDefinition resourceDef = myCtx.getResourceDefinition(myResourceType); + + Set pids = new HashSet(); + + for (Entry>> nextParamEntry : params.entrySet()) { + String nextParamName = nextParamEntry.getKey(); + RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); + if (nextParamDef != null) { + if (nextParamDef.getParamType() == SearchParamTypeEnum.TOKEN) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateToken(pids, nextAnd); + if (pids.isEmpty()) { + return new ArrayList(); + } + } + } else if (nextParamDef.getParamType() == SearchParamTypeEnum.STRING) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateString(pids, nextAnd); + if (pids.isEmpty()) { + return new ArrayList(); + } + } + } else if (nextParamDef.getParamType() == SearchParamTypeEnum.QUANTITY) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateQuantity(pids, nextAnd); + if (pids.isEmpty()) { + return new ArrayList(); + } + } + } else if (nextParamDef.getParamType() == SearchParamTypeEnum.DATE) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateDate(pids, nextAnd); + if (pids.isEmpty()) { + return new ArrayList(); + } + } + } else if (nextParamDef.getParamType() == SearchParamTypeEnum.REFERENCE) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateReference(pids, nextAnd); + if (pids.isEmpty()) { + return new ArrayList(); + } + } + } else { + throw new IllegalArgumentException("Don't know how to handle parameter of type: " + nextParamDef.getParamType()); + } + } + } + + // Execute the query and make sure we return distinct results + { + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(myTableType); + Root from = cq.from(myTableType); + if (!params.isEmpty()) { + cq.where(from.get("myId").in(pids)); + } + TypedQuery q = myEntityManager.createQuery(cq); + + List retVal = new ArrayList<>(); + for (X next : q.getResultList()) { + T resource = toResource(next); + retVal.add(resource); + } + return retVal; + } + } + + @Required + public void setTableType(Class theTableType) { + myTableType = theTableType; + } + private X toEntity(T theResource) { X retVal; try { @@ -766,4 +873,26 @@ public class FhirResourceDao return retVal; } + @Transactional(propagation = Propagation.SUPPORTS) + @Override + public MethodOutcome update(final T theResource, final IdDt theId) { + TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + X savedEntity = template.execute(new TransactionCallback() { + @Override + public X doInTransaction(TransactionStatus theStatus) { + final X entity = readEntity(theId); + final ResourceHistoryTable existing = entity.toHistory(myCtx); + + populateResourceIntoEntity(theResource, entity); + myEntityManager.persist(existing); + + entity.setUpdated(new Date()); + myEntityManager.persist(entity); + return entity; + } + }); + + return toMethodOutcome(savedEntity); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java index eb4a4d6b8b4..8482cb88f42 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao; import java.util.List; import java.util.Map; +import ca.uhn.fhir.jpa.entity.BaseResourceTable; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.primitive.IdDt; @@ -30,5 +31,9 @@ public interface IFhirResourceDao { List search(String theSpName, IQueryParameterType theValue); List searchWithAndOr(Map>> theMap); + + Class getResourceType(); + + Class> getTableType(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java index 339ff2603a2..21b16ca43ea 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java @@ -39,6 +39,17 @@ public class ResourceLink implements Serializable { @Column(name = "TARGET_RESOURCE_PID", insertable = false, updatable = false) private Long myTargetResourcePid; + public ResourceLink() { + //nothing + } + + public ResourceLink(String theSourcePath, BaseResourceTable theSourceResource, BaseResourceTable theTargetResource) { + super(); + mySourcePath = theSourcePath; + mySourceResource = theSourceResource; + myTargetResource = theTargetResource; + } + public String getSourcePath() { return mySourcePath; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java index d834c48f61b..5110aee2a05 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java @@ -22,11 +22,13 @@ import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.composite.QuantityDt; +import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -72,6 +74,29 @@ public class FhirResourceDaoTest { assertTrue(updated.before(now)); } + + @Test + public void testPersistResourceLink() { + Patient patient = new Patient(); + patient.addIdentifier("urn:system", "testPersistResourceLink01"); + IdDt patientId01 = ourPatientDao.create(patient).getId(); + + Patient patient02 = new Patient(); + patient02.addIdentifier("urn:system", "testPersistResourceLink02"); + IdDt patientId02 = ourPatientDao.create(patient02).getId(); + + Observation obs01 = new Observation(); + obs01.setApplies(new DateTimeDt(new Date())); + obs01.setSubject(new ResourceReferenceDt(Patient.class, patientId01)); + IdDt obsId01 = ourObservationDao.create(obs01).getId(); + + Observation obs02 = new Observation(); + obs01.setApplies(new DateTimeDt(new Date())); + obs01.setSubject(new ResourceReferenceDt(Patient.class, patientId01)); + IdDt obsId02 = ourObservationDao.create(obs01).getId(); + + } + @Test public void testPersistSearchParamObservationString() { Observation obs = new Observation(); diff --git a/hapi-tinder-plugin/.classpath b/hapi-tinder-plugin/.classpath index 06ae0986941..65cbcc2b672 100644 --- a/hapi-tinder-plugin/.classpath +++ b/hapi-tinder-plugin/.classpath @@ -1,6 +1,7 @@ + diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderStructuresMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderStructuresMojo.java index 697d08afa11..3857e4c3928 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderStructuresMojo.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderStructuresMojo.java @@ -192,7 +192,7 @@ public class TinderStructuresMojo extends AbstractMojo { // dtp.writeAll(dtOutputDir); // ResourceGeneratorUsingSpreadsheet rp = new ResourceGeneratorUsingSpreadsheet(); - rp.setBaseResourceNames(Arrays.asList("patient", "diagnosticorder")); + rp.setBaseResourceNames(Arrays.asList("observation")); rp.parse(); // rp.bindValueSets(vsp); diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/Child.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/Child.java index 81f6896b9c2..20b41f509ee 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/Child.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/Child.java @@ -34,6 +34,11 @@ public abstract class Child extends BaseElement { } + @Override + public String toString() { + return getClass().getSimpleName()+"[" + getName() + "]"; + } + /** * Strips off "[x]" */ diff --git a/hapi-tinder-plugin/src/main/resources/vm/dt_composite.vm b/hapi-tinder-plugin/src/main/resources/vm/dt_composite.vm index 5e36e2f7807..0245907a7c8 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/dt_composite.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/dt_composite.vm @@ -165,6 +165,16 @@ public class ${className} super(theResourceType, theResourceId); } + /** + * Constructor which creates a normal resource reference + * + * @param theResourceType The resource type + * @param theResourceId The resource ID + */ + public ResourceReferenceDt(Class theResourceType, IdDt theResourceId) { + super(theResourceType, theResourceId); + } + /** * Constructor which creates a resource reference containing the actual * resource in question. diff --git a/hapi-tinder-plugin/src/main/resources/vm/templates.vm b/hapi-tinder-plugin/src/main/resources/vm/templates.vm index 3ecc6b33d44..c1e85f51aab 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/templates.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/templates.vm @@ -62,7 +62,7 @@ *

*/ public ${child.referenceType} get${child.methodName}() { -#if ( ${child.hasMultipleTypes} == false && ${child.singleChildInstantiable} == true ) +#if ( (${child.hasMultipleTypes} == false && ${child.singleChildInstantiable} == true) || (${child.resourceRef}) ) if (${child.variableName} == null) { #if ( ${child.boundCode} && ${child.repeatable} == false ) ${child.variableName} = new ${child.referenceTypeForConstructor}(${child.bindingClass}.VALUESET_BINDER);