Java tutorial
package ca.uhn.fhir.jpa.dao; /* * #%L * HAPI FHIR JPA Server * %% * Copyright (C) 2014 - 2015 University Health Network * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.PostConstruct; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; import javax.persistence.TemporalType; import javax.persistence.Tuple; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; import javax.persistence.criteria.From; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Order; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeChildResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.BaseTag; import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity; 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.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.TagDefinition; import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseIdentifierDt; import ca.uhn.fhir.model.base.composite.BaseQuantityDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.ObjectUtil; @Transactional(propagation = Propagation.REQUIRED) public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirDao implements IFhirResourceDao<T> { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseFhirResourceDao.class); @PersistenceContext(type = PersistenceContextType.TRANSACTION) private EntityManager myEntityManager; @Autowired private PlatformTransactionManager myPlatformTransactionManager; private String myResourceName; private Class<T> myResourceType; private String mySecondaryPrimaryKeyParamName; private Set<Long> addPredicateComposite(RuntimeSearchParam theParamDef, Set<Long> thePids, List<? extends IQueryParameterType> theNextAnd) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceTable> from = cq.from(ResourceTable.class); cq.select(from.get("myId").as(Long.class)); IQueryParameterType or = theNextAnd.get(0); if (!(or instanceof CompositeParam<?, ?>)) { throw new InvalidRequestException("Invalid type for composite param (must be " + CompositeParam.class.getSimpleName() + ": " + or.getClass()); } CompositeParam<?, ?> cp = (CompositeParam<?, ?>) or; RuntimeSearchParam left = theParamDef.getCompositeOf().get(0); IQueryParameterType leftValue = cp.getLeftValue(); Predicate leftPredicate = createCompositeParamPart(builder, from, left, leftValue); RuntimeSearchParam right = theParamDef.getCompositeOf().get(1); IQueryParameterType rightValue = cp.getRightValue(); Predicate rightPredicate = createCompositeParamPart(builder, from, right, rightValue); Predicate type = builder.equal(from.get("myResourceType"), myResourceName); if (thePids.size() > 0) { Predicate inPids = (from.get("myResourcePid").in(thePids)); cq.where(builder.and(type, leftPredicate, rightPredicate, inPids)); } else { cq.where(builder.and(type, leftPredicate, rightPredicate)); } TypedQuery<Long> q = myEntityManager.createQuery(cq); return new HashSet<Long>(q.getResultList()); } private Set<Long> addPredicateDate(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) { if (theList == null || theList.isEmpty()) { return thePids; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceIndexedSearchParamDate> from = cq.from(ResourceIndexedSearchParamDate.class); cq.select(from.get("myResourcePid").as(Long.class)); List<Predicate> codePredicates = new ArrayList<Predicate>(); for (IQueryParameterType nextOr : theList) { IQueryParameterType params = nextOr; Predicate p = createPredicateDate(builder, from, params); codePredicates.add(p); } Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); Predicate type = builder.equal(from.get("myResourceType"), myResourceName); Predicate name = builder.equal(from.get("myParamName"), theParamName); if (thePids.size() > 0) { Predicate inPids = (from.get("myResourcePid").in(thePids)); cq.where(builder.and(type, name, masterCodePredicate, inPids)); } else { cq.where(builder.and(type, name, masterCodePredicate)); } TypedQuery<Long> q = myEntityManager.createQuery(cq); return new HashSet<Long>(q.getResultList()); } private Predicate addPredicateDateFromRange(CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> theFrom, DateRangeParam theRange) { Date lowerBound = theRange.getLowerBoundAsInstant(); Date upperBound = theRange.getUpperBoundAsInstant(); Predicate lb = null; if (lowerBound != null) { Predicate gt = theBuilder.greaterThanOrEqualTo(theFrom.<Date>get("myValueLow"), lowerBound); Predicate lt = theBuilder.greaterThanOrEqualTo(theFrom.<Date>get("myValueHigh"), lowerBound); lb = theBuilder.or(gt, lt); // Predicate gin = builder.isNull(from.get("myValueLow")); // Predicate lbo = builder.or(gt, gin); // Predicate lin = builder.isNull(from.get("myValueHigh")); // Predicate hbo = builder.or(lt, lin); // lb = builder.and(lbo, hbo); } Predicate ub = null; if (upperBound != null) { Predicate gt = theBuilder.lessThanOrEqualTo(theFrom.<Date>get("myValueLow"), upperBound); Predicate lt = theBuilder.lessThanOrEqualTo(theFrom.<Date>get("myValueHigh"), upperBound); ub = theBuilder.or(gt, lt); // Predicate gin = builder.isNull(from.get("myValueLow")); // Predicate lbo = builder.or(gt, gin); // Predicate lin = builder.isNull(from.get("myValueHigh")); // Predicate ubo = builder.or(lt, lin); // ub = builder.and(ubo, lbo); } if (lb != null && ub != null) { return (theBuilder.and(lb, ub)); } else if (lb != null) { return (lb); } else { return (ub); } } // private Set<Long> addPredicateComposite(String theParamName, Set<Long> thePids, List<? extends // IQueryParameterType> theList) { // } private Set<Long> addPredicateId(Set<Long> theExistingPids, Set<Long> thePids) { if (thePids == null || thePids.isEmpty()) { return Collections.emptySet(); } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceTable> from = cq.from(ResourceTable.class); cq.select(from.get("myId").as(Long.class)); Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName); Predicate idPrecidate = from.get("myId").in(thePids); cq.where(builder.and(typePredicate, idPrecidate)); TypedQuery<Long> q = myEntityManager.createQuery(cq); HashSet<Long> found = new HashSet<Long>(q.getResultList()); if (!theExistingPids.isEmpty()) { theExistingPids.retainAll(found); } return found; } private Set<Long> addPredicateLanguage(Set<Long> thePids, List<List<? extends IQueryParameterType>> theList) { if (theList == null || theList.isEmpty()) { return thePids; } if (theList.size() > 1) { throw new InvalidRequestException( "Language parameter can not have more than one AND value, found " + theList.size()); } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceTable> from = cq.from(ResourceTable.class); cq.select(from.get("myId").as(Long.class)); Set<String> values = new HashSet<String>(); for (IQueryParameterType next : theList.get(0)) { if (next instanceof StringParam) { String nextValue = ((StringParam) next).getValue(); if (isBlank(nextValue)) { continue; } values.add(nextValue); } else { throw new InternalErrorException("Lanugage parameter must be of type " + StringParam.class.getCanonicalName() + " - Got " + next.getClass().getCanonicalName()); } } if (values.isEmpty()) { return thePids; } Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName); Predicate langPredicate = from.get("myLanguage").as(String.class).in(values); Predicate masterCodePredicate = builder.and(typePredicate, langPredicate); if (thePids.size() > 0) { Predicate inPids = (from.get("myId").in(thePids)); cq.where(builder.and(masterCodePredicate, inPids)); } else { cq.where(masterCodePredicate); } TypedQuery<Long> q = myEntityManager.createQuery(cq); return new HashSet<Long>(q.getResultList()); } private Set<Long> addPredicateNumber(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) { if (theList == null || theList.isEmpty()) { return thePids; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceIndexedSearchParamNumber> from = cq.from(ResourceIndexedSearchParamNumber.class); cq.select(from.get("myResourcePid").as(Long.class)); List<Predicate> codePredicates = new ArrayList<Predicate>(); for (IQueryParameterType nextOr : theList) { IQueryParameterType params = nextOr; if (params instanceof NumberParam) { NumberParam param = (NumberParam) params; BigDecimal value = param.getValue(); if (value == null) { return thePids; } Path<Object> fromObj = from.get("myValue"); if (param.getComparator() == null) { double mul = value.doubleValue() * 1.01; double low = value.doubleValue() - mul; double high = value.doubleValue() + mul; Predicate lowPred = builder.ge(fromObj.as(Long.class), low); Predicate highPred = builder.le(fromObj.as(Long.class), high); codePredicates.add(builder.and(lowPred, highPred)); } else { switch (param.getComparator()) { case GREATERTHAN: codePredicates.add(builder.greaterThan(fromObj.as(BigDecimal.class), value)); break; case GREATERTHAN_OR_EQUALS: codePredicates.add(builder.ge(fromObj.as(BigDecimal.class), value)); break; case LESSTHAN: codePredicates.add(builder.lessThan(fromObj.as(BigDecimal.class), value)); break; case LESSTHAN_OR_EQUALS: codePredicates.add(builder.le(fromObj.as(BigDecimal.class), value)); break; } } } 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); Predicate name = builder.equal(from.get("myParamName"), theParamName); if (thePids.size() > 0) { Predicate inPids = (from.get("myResourcePid").in(thePids)); cq.where(builder.and(type, name, masterCodePredicate, inPids)); } else { cq.where(builder.and(type, name, masterCodePredicate)); } TypedQuery<Long> q = myEntityManager.createQuery(cq); return new HashSet<Long>(q.getResultList()); } private Set<Long> addPredicateQuantity(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) { if (theList == null || theList.isEmpty()) { return thePids; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceIndexedSearchParamQuantity> from = cq.from(ResourceIndexedSearchParamQuantity.class); cq.select(from.get("myResourcePid").as(Long.class)); List<Predicate> codePredicates = new ArrayList<Predicate>(); for (IQueryParameterType nextOr : theList) { IQueryParameterType params = nextOr; String systemValue; String unitsValue; QuantityCompararatorEnum cmpValue; BigDecimal valueValue; boolean approx = false; if (params instanceof BaseQuantityDt) { BaseQuantityDt param = (BaseQuantityDt) params; systemValue = param.getSystemElement().getValueAsString(); unitsValue = param.getUnitsElement().getValueAsString(); cmpValue = QuantityCompararatorEnum.VALUESET_BINDER .fromCodeString(param.getComparatorElement().getValueAsString()); valueValue = param.getValueElement().getValue(); } else if (params instanceof QuantityParam) { QuantityParam param = (QuantityParam) params; systemValue = param.getSystem().getValueAsString(); unitsValue = param.getUnits(); cmpValue = param.getComparator(); valueValue = param.getValue().getValue(); approx = param.isApproximate(); } else { throw new IllegalArgumentException("Invalid quantity type: " + params.getClass()); } Predicate system = null; if (!isBlank(systemValue)) { system = builder.equal(from.get("mySystem"), systemValue); } Predicate code = null; if (!isBlank(unitsValue)) { code = builder.equal(from.get("myUnits"), unitsValue); } Predicate num; if (cmpValue == null) { BigDecimal mul = approx ? new BigDecimal(0.1) : new BigDecimal(0.01); BigDecimal low = valueValue.subtract(valueValue.multiply(mul)); BigDecimal high = valueValue.add(valueValue.multiply(mul)); Predicate lowPred = builder.gt(from.get("myValue").as(BigDecimal.class), low); Predicate highPred = builder.lt(from.get("myValue").as(BigDecimal.class), high); num = builder.and(lowPred, highPred); } else { switch (cmpValue) { case GREATERTHAN: Expression<Number> path = from.get("myValue"); num = builder.gt(path, valueValue); break; case GREATERTHAN_OR_EQUALS: path = from.get("myValue"); num = builder.ge(path, valueValue); break; case LESSTHAN: path = from.get("myValue"); num = builder.lt(path, valueValue); break; case LESSTHAN_OR_EQUALS: path = from.get("myValue"); num = builder.le(path, valueValue); break; default: throw new IllegalStateException(cmpValue.getCode()); } } if (system == null && code == null) { codePredicates.add(num); } else if (system == null) { Predicate singleCode = builder.and(code, num); codePredicates.add(singleCode); } else if (code == null) { Predicate singleCode = builder.and(system, num); codePredicates.add(singleCode); } else { Predicate singleCode = builder.and(system, code, num); codePredicates.add(singleCode); } } Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); Predicate type = builder.equal(from.get("myResourceType"), myResourceName); Predicate name = builder.equal(from.get("myParamName"), theParamName); if (thePids.size() > 0) { Predicate inPids = (from.get("myResourcePid").in(thePids)); cq.where(builder.and(type, name, masterCodePredicate, inPids)); } else { cq.where(builder.and(type, name, masterCodePredicate)); } TypedQuery<Long> q = myEntityManager.createQuery(cq); return new HashSet<Long>(q.getResultList()); } private Set<Long> addPredicateReference(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) { assert theParamName.contains(".") == false; Set<Long> pidsToRetain = thePids; if (theList == null || theList.isEmpty()) { return pidsToRetain; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceLink> from = cq.from(ResourceLink.class); cq.select(from.get("mySourceResourcePid").as(Long.class)); List<Predicate> codePredicates = new ArrayList<Predicate>(); for (IQueryParameterType nextOr : theList) { IQueryParameterType params = nextOr; if (params instanceof ReferenceParam) { ReferenceParam ref = (ReferenceParam) params; String resourceId = ref.getValueAsQueryToken(); if (resourceId.contains("/")) { IdDt dt = new IdDt(resourceId); resourceId = dt.getIdPart(); } if (isBlank(ref.getChain())) { Long targetPid = translateForcedIdToPid(new IdDt(resourceId)); ourLog.info("Searching for resource link with target PID: {}", targetPid); Predicate eq = builder.equal(from.get("myTargetResourcePid"), targetPid); codePredicates.add(eq); } else { String chain = getContext().getResourceDefinition(myResourceType).getSearchParam(theParamName) .getPath(); BaseRuntimeChildDefinition def = getContext().newTerser().getDefinition(myResourceType, chain); if (!(def instanceof RuntimeChildResourceDefinition)) { throw new ConfigurationException("Property " + chain + " of type " + myResourceName + " is not a resource: " + def.getClass()); } List<Class<? extends IBaseResource>> resourceTypes; if (isBlank(ref.getResourceType())) { RuntimeChildResourceDefinition resDef = (RuntimeChildResourceDefinition) def; resourceTypes = resDef.getResourceTypes(); } else { resourceTypes = new ArrayList<Class<? extends IBaseResource>>(); RuntimeResourceDefinition resDef = getContext() .getResourceDefinition(ref.getResourceType()); resourceTypes.add(resDef.getImplementingClass()); } for (Class<? extends IBaseResource> nextType : resourceTypes) { RuntimeResourceDefinition typeDef = getContext().getResourceDefinition(nextType); RuntimeSearchParam param = typeDef.getSearchParam(ref.getChain()); if (param == null) { ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param); continue; } IFhirResourceDao<?> dao = getDao(nextType); if (dao == null) { ourLog.debug("Don't have a DAO for type {}", nextType.getSimpleName(), param); continue; } IQueryParameterType chainValue = toParameterType(param, resourceId); Set<Long> pids = dao.searchForIds(ref.getChain(), chainValue); if (pids.isEmpty()) { continue; } Predicate eq = from.get("myTargetResourcePid").in(pids); codePredicates.add(eq); } } } else { throw new IllegalArgumentException("Invalid token type: " + params.getClass()); } } Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); RuntimeSearchParam param = getContext().getResourceDefinition(getResourceType()) .getSearchParam(theParamName); String path = param.getPath(); Predicate type = builder.equal(from.get("mySourcePath"), path); if (pidsToRetain.size() > 0) { Predicate inPids = (from.get("mySourceResourcePid").in(pidsToRetain)); cq.where(builder.and(type, masterCodePredicate, inPids)); } else { cq.where(builder.and(type, masterCodePredicate)); } TypedQuery<Long> q = myEntityManager.createQuery(cq); return new HashSet<Long>(q.getResultList()); } private Set<Long> addPredicateString(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) { if (theList == null || theList.isEmpty()) { return thePids; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceIndexedSearchParamString> from = cq.from(ResourceIndexedSearchParamString.class); cq.select(from.get("myResourcePid").as(Long.class)); List<Predicate> codePredicates = new ArrayList<Predicate>(); for (IQueryParameterType nextOr : theList) { IQueryParameterType theParameter = nextOr; Predicate singleCode = createPredicateString(theParameter, theParamName, builder, from); codePredicates.add(singleCode); } Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); Predicate type = builder.equal(from.get("myResourceType"), myResourceName); Predicate name = builder.equal(from.get("myParamName"), theParamName); if (thePids.size() > 0) { Predicate inPids = (from.get("myResourcePid").in(thePids)); cq.where(builder.and(type, name, masterCodePredicate, inPids)); } else { cq.where(builder.and(type, name, masterCodePredicate)); } TypedQuery<Long> q = myEntityManager.createQuery(cq); return new HashSet<Long>(q.getResultList()); } private Set<Long> addPredicateToken(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) { if (theList == null || theList.isEmpty()) { return thePids; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceIndexedSearchParamToken> from = cq.from(ResourceIndexedSearchParamToken.class); cq.select(from.get("myResourcePid").as(Long.class)); List<Predicate> codePredicates = new ArrayList<Predicate>(); for (IQueryParameterType nextOr : theList) { if (nextOr instanceof TokenParam) { TokenParam id = (TokenParam) nextOr; if (id.isText()) { return addPredicateString(theParamName, thePids, theList); } } Predicate singleCode = createPredicateToken(nextOr, theParamName, builder, from); codePredicates.add(singleCode); } Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); Predicate type = builder.equal(from.get("myResourceType"), myResourceName); Predicate name = builder.equal(from.get("myParamName"), theParamName); if (thePids.size() > 0) { Predicate inPids = (from.get("myResourcePid").in(thePids)); cq.where(builder.and(type, name, masterCodePredicate, inPids)); } else { cq.where(builder.and(type, name, masterCodePredicate)); } TypedQuery<Long> q = myEntityManager.createQuery(cq); return new HashSet<Long>(q.getResultList()); } private List<IResource> addResourcesAsIncludesById(List<IResource> theListToPopulate, Set<IdDt> includePids, List<IResource> resources) { if (!includePids.isEmpty()) { ourLog.info("Loading {} included resources", includePids.size()); resources = loadResourcesById(includePids); for (IResource next : resources) { ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(next, BundleEntrySearchModeEnum.INCLUDE); } theListToPopulate.addAll(resources); } return resources; } @Override public void addTag(IdDt theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { StopWatch w = new StopWatch(); BaseHasResource entity = readEntity(theId); if (entity == null) { throw new ResourceNotFoundException(theId); } //@formatter:off for (BaseTag next : new ArrayList<BaseTag>(entity.getTags())) { if (ObjectUtil.equals(next.getTag().getTagType(), theTagType) && ObjectUtil.equals(next.getTag().getSystem(), theScheme) && ObjectUtil.equals(next.getTag().getCode(), theTerm)) { return; } } //@formatter:on entity.setHasTags(true); TagDefinition def = getTag(TagTypeEnum.TAG, theScheme, theTerm, theLabel); BaseTag newEntity = entity.addTag(def); myEntityManager.persist(newEntity); myEntityManager.merge(entity); notifyWriteCompleted(); ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() }); } @Override public DaoMethodOutcome create(final T theResource) { return create(theResource, null, true); } @Override public DaoMethodOutcome create(final T theResource, String theIfNoneExist) { return create(theResource, theIfNoneExist, true); } @Override public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing) { if (isNotBlank(theResource.getId().getIdPart())) { if (getContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { if (theResource.getId().isIdPartValidLong()) { throw new InvalidRequestException( getContext().getLocalizer().getMessage(BaseFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getId().getIdPart())); } } else { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirResourceDao.class, "failedToCreateWithClientAssignedId", theResource.getId().getIdPart())); } } return doCreate(theResource, theIfNoneExist, thePerformIndexing); } private Predicate createCompositeParamPart(CriteriaBuilder builder, Root<ResourceTable> from, RuntimeSearchParam left, IQueryParameterType leftValue) { Predicate retVal = null; switch (left.getParamType()) { case STRING: { From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = from .join("myParamsString", JoinType.INNER); retVal = createPredicateString(leftValue, left.getName(), builder, stringJoin); break; } case TOKEN: { From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = from .join("myParamsToken", JoinType.INNER); retVal = createPredicateToken(leftValue, left.getName(), builder, tokenJoin); break; } case DATE: { From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = from .join("myParamsDate", JoinType.INNER); retVal = createPredicateDate(builder, dateJoin, leftValue); break; } } if (retVal == null) { throw new InvalidRequestException( "Don't know how to handle composite parameter with type of " + left.getParamType()); } return retVal; } private Predicate createPredicateDate(CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> theFrom, IQueryParameterType theParam) { Predicate p; if (theParam instanceof DateParam) { DateParam date = (DateParam) theParam; if (!date.isEmpty()) { DateRangeParam range = new DateRangeParam(date); p = addPredicateDateFromRange(theBuilder, theFrom, range); } else { // TODO: handle missing date param? p = null; } } else if (theParam instanceof DateRangeParam) { DateRangeParam range = (DateRangeParam) theParam; p = addPredicateDateFromRange(theBuilder, theFrom, range); } else { throw new IllegalArgumentException("Invalid token type: " + theParam.getClass()); } return p; } private Predicate createPredicateString(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theFrom) { String rawSearchTerm; if (theParameter instanceof TokenParam) { TokenParam id = (TokenParam) theParameter; if (!id.isText()) { throw new IllegalStateException("Trying to process a text search on a non-text token parameter"); } rawSearchTerm = id.getValue(); } else if (theParameter instanceof StringParam) { StringParam id = (StringParam) theParameter; rawSearchTerm = id.getValue(); } else if (theParameter instanceof IPrimitiveDatatype<?>) { IPrimitiveDatatype<?> id = (IPrimitiveDatatype<?>) theParameter; rawSearchTerm = id.getValueAsString(); } else { throw new IllegalArgumentException("Invalid token type: " + theParameter.getClass()); } if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); } String likeExpression = normalizeString(rawSearchTerm); likeExpression = likeExpression.replace("%", "[%]") + "%"; Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); if (theParameter instanceof StringParam && ((StringParam) theParameter).isExact()) { Predicate exactCode = theBuilder.equal(theFrom.get("myValueExact"), rawSearchTerm); singleCode = theBuilder.and(singleCode, exactCode); } return singleCode; } private Predicate createPredicateToken(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> theFrom) { String code; String system; if (theParameter instanceof TokenParam) { TokenParam id = (TokenParam) theParameter; system = id.getSystem(); code = id.getValue(); } else if (theParameter instanceof BaseIdentifierDt) { BaseIdentifierDt id = (BaseIdentifierDt) theParameter; system = id.getSystemElement().getValueAsString(); code = id.getValueElement().getValue(); } else if (theParameter instanceof BaseCodingDt) { BaseCodingDt id = (BaseCodingDt) theParameter; system = id.getSystemElement().getValueAsString(); code = id.getCodeElement().getValue(); } else { throw new IllegalArgumentException("Invalid token type: " + theParameter.getClass()); } if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system); } if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code); } ArrayList<Predicate> singleCodePredicates = (new ArrayList<Predicate>()); if (StringUtils.isNotBlank(system)) { singleCodePredicates.add(theBuilder.equal(theFrom.get("mySystem"), system)); } else if (system == null) { // don't check the system } else { // If the system is "", we only match on null systems singleCodePredicates.add(theBuilder.isNull(theFrom.get("mySystem"))); } if (StringUtils.isNotBlank(code)) { singleCodePredicates.add(theBuilder.equal(theFrom.get("myValue"), code)); } else { singleCodePredicates.add(theBuilder.isNull(theFrom.get("myValue"))); } Predicate singleCode = theBuilder.and(singleCodePredicates.toArray(new Predicate[0])); return singleCode; } private void createSort(CriteriaBuilder theBuilder, Root<ResourceTable> theFrom, SortSpec theSort, List<Order> theOrders, List<Predicate> thePredicates) { if (theSort == null || isBlank(theSort.getParamName())) { return; } if ("_id".equals(theSort.getParamName())) { From<?, ?> forcedIdJoin = theFrom.join("myForcedId", JoinType.LEFT); if (theSort.getOrder() == null || theSort.getOrder() == SortOrderEnum.ASC) { theOrders.add(theBuilder.asc(forcedIdJoin.get("myForcedId"))); theOrders.add(theBuilder.asc(theFrom.get("myId"))); } else { theOrders.add(theBuilder.desc(forcedIdJoin.get("myForcedId"))); theOrders.add(theBuilder.desc(theFrom.get("myId"))); } createSort(theBuilder, theFrom, theSort.getChain(), theOrders, null); return; } RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceType); RuntimeSearchParam param = resourceDef.getSearchParam(theSort.getParamName()); if (param == null) { throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'"); } String joinAttrName; String sortAttrName; switch (param.getParamType()) { case STRING: joinAttrName = "myParamsString"; sortAttrName = "myValueExact"; break; case DATE: joinAttrName = "myParamsDate"; sortAttrName = "myValueLow"; break; default: throw new NotImplementedException("This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + theSort.getParamName()); } From<?, ?> stringJoin = theFrom.join(joinAttrName, JoinType.INNER); // Predicate p = theBuilder.equal(stringJoin.get("myParamName"), theSort.getParamName()); // Predicate pn = theBuilder.isNull(stringJoin.get("myParamName")); // thePredicates.add(theBuilder.or(p, pn)); if (theSort.getOrder() == null || theSort.getOrder() == SortOrderEnum.ASC) { theOrders.add(theBuilder.asc(stringJoin.get(sortAttrName))); } else { theOrders.add(theBuilder.desc(stringJoin.get(sortAttrName))); } createSort(theBuilder, theFrom, theSort.getChain(), theOrders, null); } @Override public DaoMethodOutcome delete(IdDt theId) { StopWatch w = new StopWatch(); final ResourceTable entity = readEntityLatestVersion(theId); if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version"); } ResourceTable savedEntity = updateEntity(null, entity, true, new Date()); notifyWriteCompleted(); ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); return toMethodOutcome(savedEntity, null); } @Override public DaoMethodOutcome deleteByUrl(String theUrl) { StopWatch w = new StopWatch(); Set<Long> resource = processMatchUrl(theUrl, myResourceType); if (resource.isEmpty()) { throw new ResourceNotFoundException(getContext().getLocalizer().getMessage(BaseFhirResourceDao.class, "unableToDeleteNotFound", theUrl)); } else if (resource.size() > 1) { throw new ResourceNotFoundException(getContext().getLocalizer().getMessage(BaseFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size())); } Long pid = resource.iterator().next(); ResourceTable entity = myEntityManager.find(ResourceTable.class, pid); ResourceTable savedEntity = updateEntity(null, entity, true, new Date()); notifyWriteCompleted(); ourLog.info("Processed delete on {} in {}ms", theUrl, w.getMillisAndRestart()); return toMethodOutcome(savedEntity, null); } private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing) { StopWatch w = new StopWatch(); ResourceTable entity = new ResourceTable(); entity.setResourceType(toResourceName(theResource)); if (isNotBlank(theIfNoneExist)) { Set<Long> match = processMatchUrl(theIfNoneExist, myResourceType); if (match.size() > 1) { String msg = getContext().getLocalizer().getMessage(BaseFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size()); throw new PreconditionFailedException(msg); } else if (match.size() == 1) { Long pid = match.iterator().next(); entity = myEntityManager.find(ResourceTable.class, pid); return toMethodOutcome(entity, theResource).setCreated(false); } } if (theResource.getId().isEmpty() == false) { 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"); } createForcedIdIfNeeded(entity, theResource.getId()); if (entity.getForcedId() != null) { try { translateForcedIdToPid(theResource.getId()); throw new UnprocessableEntityException(getContext().getLocalizer().getMessage( BaseFhirResourceDao.class, "duplicateCreateForcedId", theResource.getId().getIdPart())); } catch (ResourceNotFoundException e) { // good, this ID doesn't exist so we can create it } } } updateEntity(theResource, entity, false, null, thePerformIndexing, true); DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true); notifyWriteCompleted(); ourLog.info("Processed create on {} in {}ms", myResourceName, w.getMillisAndRestart()); return outcome; } @Override public TagList getAllResourceTags() { StopWatch w = new StopWatch(); TagList tags = super.getTags(myResourceType, null); ourLog.info("Processed getTags on {} in {}ms", myResourceName, w.getMillisAndRestart()); return tags; } protected abstract List<Object> getIncludeValues(FhirTerser theTerser, Include theInclude, IResource theResource, RuntimeResourceDefinition theResourceDef); public Class<T> getResourceType() { return myResourceType; } @Override public TagList getTags(IdDt theResourceId) { StopWatch w = new StopWatch(); TagList retVal = super.getTags(myResourceType, theResourceId); ourLog.info("Processed getTags on {} in {}ms", theResourceId, w.getMillisAndRestart()); return retVal; } @Override public IBundleProvider history(Date theSince) { StopWatch w = new StopWatch(); IBundleProvider retVal = super.history(myResourceName, null, theSince); ourLog.info("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart()); return retVal; } @Override public IBundleProvider history(final IdDt theId, final Date theSince) { final InstantDt end = createHistoryToTimestamp(); final String resourceType = getContext().getResourceDefinition(myResourceType).getName(); T currentTmp; try { BaseHasResource entity = readEntity(theId.toVersionless(), false); validateResourceType(entity); currentTmp = toResource(myResourceType, entity); if (ResourceMetadataKeyEnum.UPDATED.get(currentTmp).after(end.getValue())) { currentTmp = null; } } catch (ResourceNotFoundException e) { currentTmp = null; } final T current = currentTmp; String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" + (theSince != null ? " AND h.myUpdated >= :SINCE" : ""); TypedQuery<Long> countQuery = myEntityManager.createQuery(querySring, Long.class); countQuery.setParameter("PID", translateForcedIdToPid(theId)); countQuery.setParameter("RESTYPE", resourceType); countQuery.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); if (theSince != null) { countQuery.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); } int historyCount = countQuery.getSingleResult().intValue(); final int offset; final int count; if (current != null) { count = historyCount + 1; offset = 1; } else { offset = 0; count = historyCount; } if (count == 0) { throw new ResourceNotFoundException(theId); } return new IBundleProvider() { @Override public InstantDt getPublished() { return end; } @Override public List<IResource> getResources(int theFromIndex, int theToIndex) { ArrayList<IResource> retVal = new ArrayList<IResource>(); if (theFromIndex == 0 && current != null) { retVal.add(current); } TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery( "SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " + (theSince != null ? " AND h.myUpdated >= :SINCE" : "") + " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class); q.setParameter("PID", translateForcedIdToPid(theId)); q.setParameter("RESTYPE", resourceType); q.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); if (theSince != null) { q.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); } int firstResult = Math.max(0, theFromIndex - offset); q.setFirstResult(firstResult); int maxResults = (theToIndex - theFromIndex) + 1; q.setMaxResults(maxResults); List<ResourceHistoryTable> results = q.getResultList(); for (ResourceHistoryTable next : results) { if (retVal.size() == maxResults) { break; } retVal.add(toResource(myResourceType, next)); } return retVal; } @Override public Integer preferredPageSize() { return null; } @Override public int size() { return count; } }; } @Override public IBundleProvider history(Long theId, Date theSince) { StopWatch w = new StopWatch(); IBundleProvider retVal = super.history(myResourceName, theId, theSince); ourLog.info("Processed history on {} in {}ms", theId, w.getMillisAndRestart()); return retVal; } private void loadResourcesByPid(Collection<Long> theIncludePids, List<IResource> theResourceListToPopulate, BundleEntrySearchModeEnum theBundleEntryStatus) { if (theIncludePids.isEmpty()) { return; } Map<Long, Integer> position = new HashMap<Long, Integer>(); for (Long next : theIncludePids) { position.put(next, theResourceListToPopulate.size()); theResourceListToPopulate.add(null); } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class); Root<ResourceTable> from = cq.from(ResourceTable.class); cq.where(from.get("myId").in(theIncludePids)); TypedQuery<ResourceTable> q = myEntityManager.createQuery(cq); for (ResourceTable next : q.getResultList()) { Class<? extends IBaseResource> resourceType = getContext().getResourceDefinition(next.getResourceType()) .getImplementingClass(); IResource resource = (IResource) toResource(resourceType, next); Integer index = position.get(next.getId()); if (index == null) { ourLog.warn("Got back unexpected resource PID {}", next.getId()); continue; } ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(resource, theBundleEntryStatus); theResourceListToPopulate.set(index, resource); } } protected void loadReverseIncludes(List<Long> theMatches, Set<Include> theRevIncludes) { if (theMatches.size() == 0) { return; } HashSet<Long> pidsToInclude = new HashSet<Long>(); for (Include nextInclude : theRevIncludes) { boolean matchAll = "*".equals(nextInclude.getValue()); if (matchAll) { String sql = "SELECT r FROM ResourceLink r WHERE r.myTargetResourcePid IN (:target_pids)"; TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class); q.setParameter("target_pids", theMatches); List<ResourceLink> results = q.getResultList(); for (ResourceLink resourceLink : results) { pidsToInclude.add(resourceLink.getSourceResourcePid()); } } else { int colonIdx = nextInclude.getValue().indexOf(':'); if (colonIdx < 2) { continue; } String resType = nextInclude.getValue().substring(0, colonIdx); RuntimeResourceDefinition def = getContext().getResourceDefinition(resType); if (def == null) { ourLog.warn("Unknown resource type in _revinclude=" + nextInclude.getValue()); continue; } String paramName = nextInclude.getValue().substring(colonIdx + 1); RuntimeSearchParam param = def.getSearchParam(paramName); if (param == null) { ourLog.warn("Unknown param name in _revinclude=" + nextInclude.getValue()); continue; } for (String nextPath : param.getPathsSplit()) { String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r.myTargetResourcePid IN (:target_pids)"; TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class); q.setParameter("src_path", nextPath); q.setParameter("target_pids", theMatches); List<ResourceLink> results = q.getResultList(); for (ResourceLink resourceLink : results) { pidsToInclude.add(resourceLink.getSourceResourcePid()); } } } } theMatches.addAll(pidsToInclude); } @Override public MetaDt metaAddOperation(IdDt theResourceId, MetaDt theMetaAdd) { StopWatch w = new StopWatch(); BaseHasResource entity = readEntity(theResourceId); if (entity == null) { throw new ResourceNotFoundException(theResourceId); } List<TagDefinition> tags = toTagList(theMetaAdd); //@formatter:off for (TagDefinition nextDef : tags) { boolean hasTag = false; for (BaseTag next : new ArrayList<BaseTag>(entity.getTags())) { if (ObjectUtil.equals(next.getTag().getTagType(), nextDef.getTagType()) && ObjectUtil.equals(next.getTag().getSystem(), nextDef.getSystem()) && ObjectUtil.equals(next.getTag().getCode(), nextDef.getCode())) { hasTag = true; break; } } if (!hasTag) { entity.setHasTags(true); TagDefinition def = getTag(nextDef.getTagType(), nextDef.getSystem(), nextDef.getCode(), nextDef.getDisplay()); BaseTag newEntity = entity.addTag(def); myEntityManager.persist(newEntity); } } //@formatter:on myEntityManager.merge(entity); notifyWriteCompleted(); ourLog.info("Processed metaAddOperation on {} in {}ms", new Object[] { theResourceId, w.getMillisAndRestart() }); return metaGetOperation(theResourceId); } @Override public MetaDt metaDeleteOperation(IdDt theResourceId, MetaDt theMetaDel) { StopWatch w = new StopWatch(); BaseHasResource entity = readEntity(theResourceId); if (entity == null) { throw new ResourceNotFoundException(theResourceId); } List<TagDefinition> tags = toTagList(theMetaDel); //@formatter:off for (TagDefinition nextDef : tags) { for (BaseTag next : new ArrayList<BaseTag>(entity.getTags())) { if (ObjectUtil.equals(next.getTag().getTagType(), nextDef.getTagType()) && ObjectUtil.equals(next.getTag().getSystem(), nextDef.getSystem()) && ObjectUtil.equals(next.getTag().getCode(), nextDef.getCode())) { myEntityManager.remove(next); entity.getTags().remove(next); } } } //@formatter:on if (entity.getTags().isEmpty()) { entity.setHasTags(false); } myEntityManager.merge(entity); ourLog.info("Processed metaDeleteOperation on {} in {}ms", new Object[] { theResourceId.getValue(), w.getMillisAndRestart() }); return metaGetOperation(theResourceId); } @Override public MetaDt metaGetOperation() { String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t WHERE t.myResourceType = :res_type)"; TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class); q.setParameter("res_type", myResourceName); List<TagDefinition> tagDefinitions = q.getResultList(); MetaDt retVal = super.toMetaDt(tagDefinitions); return retVal; } @Override public MetaDt metaGetOperation(IdDt theId) { Long pid = super.translateForcedIdToPid(theId); String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t WHERE t.myResourceType = :res_type AND t.myResourceId = :res_id)"; TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class); q.setParameter("res_type", myResourceName); q.setParameter("res_id", pid); List<TagDefinition> tagDefinitions = q.getResultList(); MetaDt retVal = super.toMetaDt(tagDefinitions); return retVal; } @PostConstruct public void postConstruct() { RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType); myResourceName = def.getName(); if (mySecondaryPrimaryKeyParamName != null) { RuntimeSearchParam sp = def.getSearchParam(mySecondaryPrimaryKeyParamName); if (sp == null) { throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); } if (sp.getParamType() != RestSearchParameterTypeEnum.TOKEN) { throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "] is not a token type, only token is supported"); } } } @Override public T read(IdDt theId) { validateResourceTypeAndThrowIllegalArgumentException(theId); StopWatch w = new StopWatch(); BaseHasResource entity = readEntity(theId); validateResourceType(entity); T retVal = toResource(myResourceType, entity); InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(retVal); if (deleted != null && !deleted.isEmpty()) { throw new ResourceGoneException("Resource was deleted at " + deleted.getValueAsString()); } ourLog.info("Processed read on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); return retVal; } @Override public BaseHasResource readEntity(IdDt theId) { boolean checkForForcedId = true; BaseHasResource entity = readEntity(theId, checkForForcedId); return entity; } @Override public BaseHasResource readEntity(IdDt theId, boolean theCheckForForcedId) { validateResourceTypeAndThrowIllegalArgumentException(theId); Long pid = translateForcedIdToPid(theId); BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid); if (theId.hasVersionIdPart()) { if (entity.getVersion() != theId.getVersionIdPartAsLong()) { entity = null; } } if (entity == null) { if (theId.hasVersionIdPart()) { TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery( "SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class); q.setParameter("RID", pid); q.setParameter("RTYP", myResourceName); q.setParameter("RVER", theId.getVersionIdPartAsLong()); entity = q.getSingleResult(); } if (entity == null) { throw new ResourceNotFoundException(theId); } } validateResourceType(entity); if (theCheckForForcedId) { validateGivenIdIsAppropriateToRetrieveResource(theId, entity); } return entity; } private ResourceTable readEntityLatestVersion(IdDt theId) { ResourceTable entity = myEntityManager.find(ResourceTable.class, translateForcedIdToPid(theId)); if (entity == null) { throw new ResourceNotFoundException(theId); } validateGivenIdIsAppropriateToRetrieveResource(theId, entity); return entity; } @Override public void removeTag(IdDt theId, TagTypeEnum theTagType, String theScheme, String theTerm) { StopWatch w = new StopWatch(); BaseHasResource entity = readEntity(theId); if (entity == null) { throw new ResourceNotFoundException(theId); } //@formatter:off for (BaseTag next : new ArrayList<BaseTag>(entity.getTags())) { if (ObjectUtil.equals(next.getTag().getTagType(), theTagType) && ObjectUtil.equals(next.getTag().getSystem(), theScheme) && ObjectUtil.equals(next.getTag().getCode(), theTerm)) { myEntityManager.remove(next); entity.getTags().remove(next); } } //@formatter:on if (entity.getTags().isEmpty()) { entity.setHasTags(false); } myEntityManager.merge(entity); ourLog.info("Processed remove tag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId.getValue(), w.getMillisAndRestart() }); } @Override public IBundleProvider search(Map<String, IQueryParameterType> theParams) { SearchParameterMap map = new SearchParameterMap(); for (Entry<String, IQueryParameterType> nextEntry : theParams.entrySet()) { map.add(nextEntry.getKey(), (nextEntry.getValue())); } return search(map); } @Override public IBundleProvider search(final SearchParameterMap theParams) { StopWatch w = new StopWatch(); final InstantDt now = InstantDt.withCurrentTime(); Set<Long> loadPids; if (theParams.isEmpty()) { loadPids = new HashSet<Long>(); CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Tuple> cq = builder.createTupleQuery(); Root<ResourceTable> from = cq.from(ResourceTable.class); cq.multiselect(from.get("myId").as(Long.class)); Predicate typeEquals = builder.equal(from.get("myResourceType"), myResourceName); Predicate notDeleted = builder.isNull(from.get("myDeleted")); cq.where(builder.and(typeEquals, notDeleted)); TypedQuery<Tuple> query = myEntityManager.createQuery(cq); for (Tuple next : query.getResultList()) { loadPids.add(next.get(0, Long.class)); } } else { loadPids = searchForIdsWithAndOr(theParams); if (loadPids.isEmpty()) { return new SimpleBundleProvider(); } } final List<Long> pids; // Handle sorting if any was provided if (theParams.getSort() != null && isNotBlank(theParams.getSort().getParamName())) { List<Order> orders = new ArrayList<Order>(); List<Predicate> predicates = new ArrayList<Predicate>(); CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Tuple> cq = builder.createTupleQuery(); Root<ResourceTable> from = cq.from(ResourceTable.class); predicates.add(from.get("myId").in(loadPids)); createSort(builder, from, theParams.getSort(), orders, predicates); if (orders.size() > 0) { Set<Long> originalPids = loadPids; loadPids = new LinkedHashSet<Long>(); cq.multiselect(from.get("myId").as(Long.class)); cq.where(predicates.toArray(new Predicate[0])); cq.orderBy(orders); TypedQuery<Tuple> query = myEntityManager.createQuery(cq); for (Tuple next : query.getResultList()) { loadPids.add(next.get(0, Long.class)); } ourLog.info("Sort PID order is now: {}", loadPids); pids = new ArrayList<Long>(loadPids); // Any ressources which weren't matched by the sort get added to the bottom for (Long next : originalPids) { if (loadPids.contains(next) == false) { pids.add(next); } } } else { pids = new ArrayList<Long>(loadPids); } } else { pids = new ArrayList<Long>(loadPids); } // Load _revinclude resources if (theParams.getRevIncludes() != null && theParams.getRevIncludes().isEmpty() == false) { loadReverseIncludes(pids, theParams.getRevIncludes()); } IBundleProvider retVal = new IBundleProvider() { @Override public InstantDt getPublished() { return now; } @Override public List<IResource> getResources(final int theFromIndex, final int theToIndex) { TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); return template.execute(new TransactionCallback<List<IResource>>() { @Override public List<IResource> doInTransaction(TransactionStatus theStatus) { List<Long> pidsSubList = pids.subList(theFromIndex, theToIndex); // Execute the query and make sure we return distinct results List<IResource> retVal = new ArrayList<IResource>(); loadResourcesByPid(pidsSubList, retVal, BundleEntrySearchModeEnum.MATCH); /* * Load _include resources - Note that _revincludes are handled differently * than _include ones, as they are counted towards the total count and paged, * so they are loaded outside the bundle provider */ if (theParams.getIncludes() != null && theParams.getIncludes().isEmpty() == false) { Set<IdDt> previouslyLoadedPids = new HashSet<IdDt>(); for (IResource next : retVal) { previouslyLoadedPids.add(next.getId().toUnqualifiedVersionless()); } Set<IdDt> includePids = new HashSet<IdDt>(); List<IResource> resources = retVal; do { includePids.clear(); FhirTerser t = getContext().newTerser(); for (Include next : theParams.getIncludes()) { for (IResource nextResource : resources) { RuntimeResourceDefinition def = getContext() .getResourceDefinition(nextResource); List<Object> values = getIncludeValues(t, next, nextResource, def); for (Object object : values) { if (object == null) { continue; } if (!(object instanceof BaseResourceReferenceDt)) { throw new InvalidRequestException("Path '" + next.getValue() + "' produced non ResourceReferenceDt value: " + object.getClass()); } BaseResourceReferenceDt rr = (BaseResourceReferenceDt) object; if (rr.getReference().isEmpty()) { continue; } if (rr.getReference().isLocal()) { continue; } IdDt nextId = rr.getReference().toUnqualified(); if (!previouslyLoadedPids.contains(nextId)) { includePids.add(nextId); previouslyLoadedPids.add(nextId); } } } } resources = addResourcesAsIncludesById(retVal, includePids, resources); } while (includePids.size() > 0 && previouslyLoadedPids.size() < getConfig().getIncludeLimit()); if (previouslyLoadedPids.size() >= getConfig().getIncludeLimit()) { OperationOutcome oo = new OperationOutcome(); oo.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails( "Not all _include resources were actually included as the request surpassed the limit of " + getConfig().getIncludeLimit() + " resources"); retVal.add(0, oo); } } return retVal; } }); } @Override public Integer preferredPageSize() { return theParams.getCount(); } @Override public int size() { return pids.size(); } }; ourLog.info("Processed search for {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() }); return retVal; } @Override public IBundleProvider search(String theParameterName, IQueryParameterType theValue) { return search(Collections.singletonMap(theParameterName, theValue)); } @Override public Set<Long> searchForIds(Map<String, IQueryParameterType> theParams) { SearchParameterMap map = new SearchParameterMap(); for (Entry<String, IQueryParameterType> nextEntry : theParams.entrySet()) { map.add(nextEntry.getKey(), (nextEntry.getValue())); } return searchForIdsWithAndOr(map); } @Override public Set<Long> searchForIds(String theParameterName, IQueryParameterType theValue) { return searchForIds(Collections.singletonMap(theParameterName, theValue)); } @Override public Set<Long> searchForIdsWithAndOr(SearchParameterMap theParams) { SearchParameterMap params = theParams; if (params == null) { params = new SearchParameterMap(); } RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceType); Set<Long> pids = new HashSet<Long>(); for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) { String nextParamName = nextParamEntry.getKey(); if (nextParamName.equals("_id")) { if (nextParamEntry.getValue().isEmpty()) { continue; } else if (nextParamEntry.getValue().size() > 1) { throw new InvalidRequestException( "AND queries not supported for _id (Multiple instances of this param found)"); } else { Set<Long> joinPids = new HashSet<Long>(); List<? extends IQueryParameterType> nextValue = nextParamEntry.getValue().get(0); if (nextValue == null || nextValue.size() == 0) { continue; } else { for (IQueryParameterType next : nextValue) { String value = next.getValueAsQueryToken(); IdDt valueId = new IdDt(value); try { long valueLong = translateForcedIdToPid(valueId); joinPids.add(valueLong); } catch (ResourceNotFoundException e) { // This isn't an error, just means no result found } } if (joinPids.isEmpty()) { continue; } } pids = addPredicateId(pids, joinPids); if (pids.isEmpty()) { return new HashSet<Long>(); } if (pids.isEmpty()) { pids.addAll(joinPids); } else { pids.retainAll(joinPids); } } } else if (nextParamName.equals("_language")) { pids = addPredicateLanguage(pids, nextParamEntry.getValue()); } else { RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); if (nextParamDef != null) { switch (nextParamDef.getParamType()) { case DATE: for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) { pids = addPredicateDate(nextParamName, pids, nextAnd); if (pids.isEmpty()) { return new HashSet<Long>(); } } break; case QUANTITY: for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) { pids = addPredicateQuantity(nextParamName, pids, nextAnd); if (pids.isEmpty()) { return new HashSet<Long>(); } } break; case REFERENCE: for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) { pids = addPredicateReference(nextParamName, pids, nextAnd); if (pids.isEmpty()) { return new HashSet<Long>(); } } break; case STRING: for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) { pids = addPredicateString(nextParamName, pids, nextAnd); if (pids.isEmpty()) { return new HashSet<Long>(); } } break; case TOKEN: for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) { pids = addPredicateToken(nextParamName, pids, nextAnd); if (pids.isEmpty()) { return new HashSet<Long>(); } } break; case NUMBER: for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) { pids = addPredicateNumber(nextParamName, pids, nextAnd); if (pids.isEmpty()) { return new HashSet<Long>(); } } break; case COMPOSITE: for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) { pids = addPredicateComposite(nextParamDef, pids, nextAnd); if (pids.isEmpty()) { return new HashSet<Long>(); } } break; } } } } return pids; } @SuppressWarnings("unchecked") @Required public void setResourceType(Class<? extends IResource> theTableType) { myResourceType = (Class<T>) theTableType; } /** * If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to share the same value. */ public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) { mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName; } private DaoMethodOutcome toMethodOutcome(final ResourceTable theEntity, IResource theResource) { DaoMethodOutcome outcome = new DaoMethodOutcome(); outcome.setId(theEntity.getIdDt()); outcome.setEntity(theEntity); outcome.setResource(theResource); if (theResource != null) { theResource.setId(theEntity.getIdDt()); } return outcome; } private IQueryParameterType toParameterType(RuntimeSearchParam theParam) { IQueryParameterType qp; switch (theParam.getParamType()) { case DATE: qp = new DateParam(); break; case NUMBER: qp = new NumberParam(); break; case QUANTITY: qp = new QuantityParam(); break; case STRING: qp = new StringParam(); break; case TOKEN: qp = new TokenParam(); break; case COMPOSITE: List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf(); if (compositeOf.size() != 2) { throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this."); } IQueryParameterType leftParam = toParameterType(compositeOf.get(0)); IQueryParameterType rightParam = toParameterType(compositeOf.get(1)); qp = new CompositeParam<IQueryParameterType, IQueryParameterType>(leftParam, rightParam); break; case REFERENCE: default: throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType()); } return qp; } private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theValueAsQueryToken) { IQueryParameterType qp = toParameterType(theParam); qp.setValueAsQueryToken(null, theValueAsQueryToken); return qp; } private ArrayList<TagDefinition> toTagList(MetaDt theMeta) { ArrayList<TagDefinition> retVal = new ArrayList<TagDefinition>(); for (CodingDt next : theMeta.getTag()) { retVal.add(new TagDefinition(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay())); } for (CodingDt next : theMeta.getSecurity()) { retVal.add(new TagDefinition(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay())); } for (UriDt next : theMeta.getProfile()) { retVal.add(new TagDefinition(TagTypeEnum.PROFILE, BaseFhirDao.NS_JPA_PROFILE, next.getValue(), null)); } return retVal; } @Override public DaoMethodOutcome update(T theResource) { return update(theResource, null); } @Override public DaoMethodOutcome update(T theResource, String theMatchUrl) { return update(theResource, theMatchUrl, true); } @Override public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing) { StopWatch w = new StopWatch(); final ResourceTable entity; IdDt resourceId; if (isNotBlank(theMatchUrl)) { Set<Long> match = processMatchUrl(theMatchUrl, myResourceType); if (match.size() > 1) { String msg = getContext().getLocalizer().getMessage(BaseFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size()); throw new PreconditionFailedException(msg); } else if (match.size() == 1) { Long pid = match.iterator().next(); entity = myEntityManager.find(ResourceTable.class, pid); resourceId = entity.getIdDt(); } else { return create(theResource); } } else { resourceId = theResource.getId(); if (resourceId == null || isBlank(resourceId.getIdPart())) { throw new InvalidRequestException("Can not update a resource with no ID"); } try { entity = readEntityLatestVersion(resourceId); } catch (ResourceNotFoundException e) { if (Character.isDigit(theResource.getId().getIdPart().charAt(0))) { throw new InvalidRequestException( getContext().getLocalizer().getMessage(BaseFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getId().getIdPart())); } return doCreate(theResource, null, thePerformIndexing); } } if (resourceId.hasVersionIdPart() && resourceId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { throw new InvalidRequestException( "Trying to update " + resourceId + " but this is not the current version"); } ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true); notifyWriteCompleted(); ourLog.info("Processed update on {} in {}ms", resourceId, w.getMillisAndRestart()); return toMethodOutcome(savedEntity, theResource).setCreated(false); } private void validateGivenIdIsAppropriateToRetrieveResource(IdDt theId, BaseHasResource entity) { if (entity.getForcedId() != null) { if (theId.isIdPartValidLong()) { // This means that the resource with the given numeric ID exists, but it has a "forced ID", meaning that // as far as the outside world is concerned, the given ID doesn't exist (it's just an internal pointer // to the // forced ID) throw new ResourceNotFoundException(theId); } } } private void validateResourceType(BaseHasResource entity) { if (!myResourceName.equals(entity.getResourceType())) { throw new ResourceNotFoundException( "Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " + entity.getResourceType()); } } private void validateResourceTypeAndThrowIllegalArgumentException(IdDt theId) { if (theId.hasResourceType() && !theId.getResourceType().equals(myResourceName)) { throw new IllegalArgumentException("Incorrect resource type (" + theId.getResourceType() + ") for this DAO, wanted: " + myResourceName); } } }