Improve performance of _lastUpdated queries in JPA server

This commit is contained in:
James Agnew 2016-03-14 06:31:45 -04:00
parent 6ddf91d9e2
commit 00ced6a652
15 changed files with 622 additions and 445 deletions

View File

@ -71,7 +71,6 @@ import ca.uhn.fhir.model.api.IValueSetEnumBinder;
import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Compartment;
import ca.uhn.fhir.model.api.annotation.Compartments;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Extension;
@ -708,23 +707,24 @@ class ModelScanner {
for (Field nextField : theClass.getFields()) {
SearchParamDefinition searchParam = pullAnnotation(nextField, SearchParamDefinition.class);
if (searchParam != null) {
RestSearchParameterTypeEnum paramType = RestSearchParameterTypeEnum.valueOf(searchParam.type().toUpperCase());
RestSearchParameterTypeEnum paramType = RestSearchParameterTypeEnum.forCode(searchParam.type().toLowerCase());
if (paramType == null) {
throw new ConfigurationException("Search param " + searchParam.name() + " has an invalid type: " + searchParam.type());
}
Set<String> providesMembershipInCompartments = null;
providesMembershipInCompartments = new HashSet<String>();
for (Compartment next : searchParam.providesMembershipIn()) {
if (paramType != RestSearchParameterTypeEnum.REFERENCE) {
throw new ConfigurationException("Search param " + searchParam.name() + " provides compartment membershit but is not of type 'reference'");
}
providesMembershipInCompartments.add(next.name());
}
if (paramType == RestSearchParameterTypeEnum.COMPOSITE) {
compositeFields.put(nextField, searchParam);
continue;
}
Set<String> providesMembershipInCompartments = null;
Compartments compartmentsAnnotation = pullAnnotation(nextField, Compartments.class);
if (compartmentsAnnotation != null) {
providesMembershipInCompartments = new HashSet<String>();
for (Compartment next : compartmentsAnnotation.providesMembershipIn()) {
providesMembershipInCompartments.add(next.name());
}
}
RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), paramType, providesMembershipInCompartments);
theResourceDef.addSearchParam(param);

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -37,6 +38,7 @@ import ca.uhn.fhir.util.UrlUtil;
public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition<IBaseResource> {
private RuntimeResourceDefinition myBaseDefinition;
private Map<String, List<RuntimeSearchParam>> myCompartmentNameToSearchParams;
private FhirContext myContext;
private String myId;
private Map<String, RuntimeSearchParam> myNameToSearchParam = new LinkedHashMap<String, RuntimeSearchParam>();
@ -134,6 +136,17 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
return mySearchParams;
}
/**
* Will not return null
*/
public List<RuntimeSearchParam> getSearchParamsForCompartmentName(String theCompartmentName) {
List<RuntimeSearchParam> retVal = myCompartmentNameToSearchParams.get(theCompartmentName);
if (retVal == null) {
return Collections.emptyList();
}
return retVal;
}
public FhirVersionEnum getStructureVersion() {
return myStructureVersion;
}
@ -161,6 +174,19 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
});
mySearchParams = Collections.unmodifiableList(searchParams);
Map<String, List<RuntimeSearchParam>> compartmentNameToSearchParams = new HashMap<String, List<RuntimeSearchParam>>();
for (RuntimeSearchParam next : searchParams) {
if (next.getProvidesMembershipInCompartments() != null) {
for (String nextCompartment : next.getProvidesMembershipInCompartments()) {
if (!compartmentNameToSearchParams.containsKey(nextCompartment)) {
compartmentNameToSearchParams.put(nextCompartment, new ArrayList<RuntimeSearchParam>());
}
compartmentNameToSearchParams.get(nextCompartment).add(next);
}
}
}
myCompartmentNameToSearchParams = Collections.unmodifiableMap(compartmentNameToSearchParams);
Class<?> target = getImplementingClass();
myBaseDefinition = this;
do {

View File

@ -24,16 +24,16 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value= {})
public @interface Compartment {
/**
* This may only be populated on a reference field. On such a field, places the containing
* This may only be populated on a reference search paramater field. On such a field, places the containing
* resource in a compartment with the name(s) specified by the given strings, where the compartment
* belongs to the target resource. For example, this field could be populated with <code>Patient</code> on
* the <code>Observation.subject</code> field.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value= {})
public @interface Compartment {
String name();
}

View File

@ -1,34 +0,0 @@
package ca.uhn.fhir.model.api.annotation;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value= {ElementType.FIELD})
public @interface Compartments {
Compartment[] providesMembershipIn();
}

View File

@ -66,4 +66,10 @@ public @interface SearchParamDefinition {
*/
Class<? extends IBaseResource>[] target() default {};
/**
* Indicates that this field indicates that resources linked to by this parameter
* (must be a reference parameter) place the resource in the given compartment.
*/
Compartment[] providesMembershipIn() default {};
}

View File

@ -132,7 +132,7 @@ public enum RestSearchParameterTypeEnum {
/**
* Returns the enumerated value associated with this code
*/
public RestSearchParameterTypeEnum forCode(String theCode) {
public static RestSearchParameterTypeEnum forCode(String theCode) {
RestSearchParameterTypeEnum retVal = CODE_TO_ENUM.get(theCode);
return retVal;
}
@ -141,6 +141,8 @@ public enum RestSearchParameterTypeEnum {
* Converts codes to their respective enumerated values
*/
public static final IValueSetEnumBinder<RestSearchParameterTypeEnum> VALUESET_BINDER = new IValueSetEnumBinder<RestSearchParameterTypeEnum>() {
private static final long serialVersionUID = 1L;
@Override
public String toCodeString(RestSearchParameterTypeEnum theEnum) {
return theEnum.getCode();

View File

@ -46,6 +46,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
import ca.uhn.fhir.context.RuntimeChildDirectResource;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
@ -63,6 +64,16 @@ public class FhirTerser {
myContext = theContext;
}
private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) {
if (theChildDefinition == null)
return null;
if (theCurrentList == null || theCurrentList.isEmpty())
return new ArrayList<String>(Arrays.asList(theChildDefinition.getElementName()));
List<String> newList = new ArrayList<String>(theCurrentList);
newList.add(theChildDefinition.getElementName());
return newList;
}
private void addUndeclaredExtensions(IBase theElement, BaseRuntimeElementDefinition<?> theDefinition, BaseRuntimeChildDefinition theChildDefinition, IModelVisitor theCallback) {
if (theElement instanceof ISupportsUndeclaredExtensions) {
ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement;
@ -88,6 +99,51 @@ public class FhirTerser {
}
/**
* Clones all values from a source object into the equivalent fields in a target object
* @param theSource The source object (must not be null)
* @param theTarget The target object to copy values into (must not be null)
* @param theIgnoreMissingFields The ignore fields in the target which do not exist (if false, an exception will be thrown if the target is unable to accept a value from the source)
*/
public void cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) {
Validate.notNull(theSource, "theSource must not be null");
Validate.notNull(theTarget, "theTarget must not be null");
if (theSource instanceof IPrimitiveType<?>) {
if (theTarget instanceof IPrimitiveType<?>) {
((IPrimitiveType<?>)theTarget).setValueAsString(((IPrimitiveType<?>)theSource).getValueAsString());
return;
} else {
if (theIgnoreMissingFields) {
return;
} else {
throw new DataFormatException("Can not copy value from primitive of type " + theSource.getClass().getName() + " into type " + theTarget.getClass().getName());
}
}
}
BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass());
BaseRuntimeElementCompositeDefinition<?> targetDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass());
for (BaseRuntimeChildDefinition nextChild : sourceDef.getChildren()) {
for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) {
BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(nextChild.getElementName());
if (targetChild == null) {
if (theIgnoreMissingFields) {
continue;
} else {
throw new DataFormatException("Type " + theTarget.getClass().getName() + " does not have a child with name " + nextChild.getElementName());
}
}
IBase target = targetChild.getChildByName(nextChild.getElementName()).newInstance();
targetChild.getMutator().addValue(theTarget, target);
cloneInto(nextValue, target, theIgnoreMissingFields);
}
}
}
/**
* Returns a list containing all child elements (including the resource itself) which are <b>non-empty</b> and are either of the exact type specified, or are a subclass of that type.
* <p>
@ -186,6 +242,33 @@ public class FhirTerser {
}
public Object getSingleValueOrNull(IBase theTarget, String thePath) {
Class<Object> wantedType = Object.class;
return getSingleValueOrNull(theTarget, thePath, wantedType);
}
public <T> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) {
Validate.notNull(theTarget, "theTarget must not be null");
Validate.notBlank(thePath, "thePath must not be empty");
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass());
if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
throw new IllegalArgumentException("Target is not a composite type: " + theTarget.getClass().getName());
}
BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def;
Object currentObj = theTarget;
List<String> parts = Arrays.asList(thePath.split("\\."));
List<T> retVal = getValues(currentDef, currentObj, parts, theWantedType);
if (retVal.isEmpty()) {
return null;
} else {
return retVal.get(0);
}
}
@SuppressWarnings("unchecked")
private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass) {
String name = theSubList.get(0);
@ -252,105 +335,38 @@ public class FhirTerser {
return getValues(currentDef, currentObj, subList, theWantedClass);
}
private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) {
if (theChildDefinition == null)
return null;
if (theCurrentList == null || theCurrentList.isEmpty())
return new ArrayList<String>(Arrays.asList(theChildDefinition.getElementName()));
List<String> newList = new ArrayList<String>(theCurrentList);
newList.add(theChildDefinition.getElementName());
return newList;
}
/**
* Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code>
* belonging to resource <code>theTarget</code>
*
* @param theCompartmentName The name of the compartment
* @param theSource The potential member of the compartment
* @param theTarget The owner of the compartment
* @return <code>true</code> if <code>theSource</code> is in the compartment
*/
public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IBaseResource theTarget) {
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
Validate.notNull(theSource, "theSource must not be null");
Validate.notNull(theTarget, "theTarget must not be null");
Validate.notBlank(theTarget.getIdElement().getIdPart(), "theTarget must have a populated ID (theTarget.getIdElement().getIdPart() does not return a value)");
private void visit(IdentityHashMap<Object, Object> theStack, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition);
RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(theTarget);
String wantRef = targetDef.getName() + '/' + theTarget.getIdElement().getIdPart();
if (theStack.put(theElement, theElement) != null) {
return;
}
theCallback.acceptElement(theElement, pathToElement, theChildDefinition, theDefinition);
addUndeclaredExtensions(theElement, theDefinition, theChildDefinition, theCallback);
BaseRuntimeElementDefinition<?> def = theDefinition;
if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) {
def = myContext.getElementDefinition(theElement.getClass());
}
switch (def.getChildType()) {
case ID_DATATYPE:
case PRIMITIVE_XHTML_HL7ORG:
case PRIMITIVE_XHTML:
case PRIMITIVE_DATATYPE:
// These are primitive types
break;
case RESOURCE_REF:
IBaseReference resRefDt = (IBaseReference) theElement;
if (resRefDt.getReferenceElement().getValue() == null && resRefDt.getResource() != null) {
IBaseResource theResource = resRefDt.getResource();
if (theResource.getIdElement() == null || theResource.getIdElement().isEmpty() || theResource.getIdElement().isLocal()) {
def = myContext.getResourceDefinition(theResource);
visit(theStack, theResource, pathToElement, null, def, theCallback);
RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource);
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
for (RuntimeSearchParam nextParam : params) {
for (String nextPath : nextParam.getPathsSplit()) {
for (IBaseReference nextValue : getValues(theSource, nextPath, IBaseReference.class)) {
String nextRef = nextValue.getReferenceElement().toUnqualifiedVersionless().getValue();
if (wantRef.equals(nextRef)) {
return true;
}
}
break;
case RESOURCE:
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE: {
BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
List<?> values = nextChild.getAccessor().getValues(theElement);
if (values != null) {
for (Object nextValueObject : values) {
IBase nextValue;
try {
nextValue = (IBase) nextValueObject;
} catch (ClassCastException e) {
String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName();
throw new ClassCastException(s);
}
if (nextValue == null) {
continue;
}
if (nextValue.isEmpty()) {
continue;
}
BaseRuntimeElementDefinition<?> childElementDef;
childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
if (childElementDef == null) {
childElementDef = myContext.getElementDefinition(nextValue.getClass());
}
if (nextChild instanceof RuntimeChildDirectResource) {
// Don't descend into embedded resources
theCallback.acceptElement(nextValue, null, nextChild, childElementDef);
} else {
visit(theStack, nextValue, pathToElement, nextChild, childElementDef, theCallback);
}
}
}
}
break;
}
case CONTAINED_RESOURCES: {
BaseContainedDt value = (BaseContainedDt) theElement;
for (IResource next : value.getContainedResources()) {
def = myContext.getResourceDefinition(next);
visit(theStack, next, pathToElement, null, def, theCallback);
}
break;
}
case CONTAINED_RESOURCE_LIST:
case EXTENSION_DECLARED:
case UNDECL_EXT: {
throw new IllegalStateException("state should not happen: " + def.getChildType());
}
}
theStack.remove(theElement);
return false;
}
private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath,
@ -516,75 +532,94 @@ public class FhirTerser {
visit(theResource, null, def, theVisitor, new ArrayList<IBase>(), new ArrayList<BaseRuntimeChildDefinition>(), new ArrayList<BaseRuntimeElementDefinition<?>>());
}
public Object getSingleValueOrNull(IBase theTarget, String thePath) {
Class<Object> wantedType = Object.class;
private void visit(IdentityHashMap<Object, Object> theStack, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition);
return getSingleValueOrNull(theTarget, thePath, wantedType);
}
public <T> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) {
Validate.notNull(theTarget, "theTarget must not be null");
Validate.notBlank(thePath, "thePath must not be empty");
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass());
if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
throw new IllegalArgumentException("Target is not a composite type: " + theTarget.getClass().getName());
}
BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def;
Object currentObj = theTarget;
List<String> parts = Arrays.asList(thePath.split("\\."));
List<T> retVal = getValues(currentDef, currentObj, parts, theWantedType);
if (retVal.isEmpty()) {
return null;
} else {
return retVal.get(0);
}
}
/**
* Clones all values from a source object into the equivalent fields in a target object
* @param theSource The source object (must not be null)
* @param theTarget The target object to copy values into (must not be null)
* @param theIgnoreMissingFields The ignore fields in the target which do not exist (if false, an exception will be thrown if the target is unable to accept a value from the source)
*/
public void cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) {
Validate.notNull(theSource, "theSource must not be null");
Validate.notNull(theTarget, "theTarget must not be null");
if (theSource instanceof IPrimitiveType<?>) {
if (theTarget instanceof IPrimitiveType<?>) {
((IPrimitiveType<?>)theTarget).setValueAsString(((IPrimitiveType<?>)theSource).getValueAsString());
if (theStack.put(theElement, theElement) != null) {
return;
} else {
if (theIgnoreMissingFields) {
return;
} else {
throw new DataFormatException("Can not copy value from primitive of type " + theSource.getClass().getName() + " into type " + theTarget.getClass().getName());
}
}
}
BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass());
BaseRuntimeElementCompositeDefinition<?> targetDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass());
theCallback.acceptElement(theElement, pathToElement, theChildDefinition, theDefinition);
addUndeclaredExtensions(theElement, theDefinition, theChildDefinition, theCallback);
for (BaseRuntimeChildDefinition nextChild : sourceDef.getChildren()) {
for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) {
BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(nextChild.getElementName());
if (targetChild == null) {
if (theIgnoreMissingFields) {
BaseRuntimeElementDefinition<?> def = theDefinition;
if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) {
def = myContext.getElementDefinition(theElement.getClass());
}
switch (def.getChildType()) {
case ID_DATATYPE:
case PRIMITIVE_XHTML_HL7ORG:
case PRIMITIVE_XHTML:
case PRIMITIVE_DATATYPE:
// These are primitive types
break;
case RESOURCE_REF:
IBaseReference resRefDt = (IBaseReference) theElement;
if (resRefDt.getReferenceElement().getValue() == null && resRefDt.getResource() != null) {
IBaseResource theResource = resRefDt.getResource();
if (theResource.getIdElement() == null || theResource.getIdElement().isEmpty() || theResource.getIdElement().isLocal()) {
def = myContext.getResourceDefinition(theResource);
visit(theStack, theResource, pathToElement, null, def, theCallback);
}
}
break;
case RESOURCE:
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE: {
BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
List<?> values = nextChild.getAccessor().getValues(theElement);
if (values != null) {
for (Object nextValueObject : values) {
IBase nextValue;
try {
nextValue = (IBase) nextValueObject;
} catch (ClassCastException e) {
String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName();
throw new ClassCastException(s);
}
if (nextValue == null) {
continue;
}
if (nextValue.isEmpty()) {
continue;
}
BaseRuntimeElementDefinition<?> childElementDef;
childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
if (childElementDef == null) {
childElementDef = myContext.getElementDefinition(nextValue.getClass());
}
if (nextChild instanceof RuntimeChildDirectResource) {
// Don't descend into embedded resources
theCallback.acceptElement(nextValue, null, nextChild, childElementDef);
} else {
throw new DataFormatException("Type " + theTarget.getClass().getName() + " does not have a child with name " + nextChild.getElementName());
visit(theStack, nextValue, pathToElement, nextChild, childElementDef, theCallback);
}
}
}
}
break;
}
case CONTAINED_RESOURCES: {
BaseContainedDt value = (BaseContainedDt) theElement;
for (IResource next : value.getContainedResources()) {
def = myContext.getResourceDefinition(next);
visit(theStack, next, pathToElement, null, def, theCallback);
}
break;
}
case CONTAINED_RESOURCE_LIST:
case EXTENSION_DECLARED:
case UNDECL_EXT: {
throw new IllegalStateException("state should not happen: " + def.getChildType());
}
}
IBase target = targetChild.getChildByName(nextChild.getElementName()).newInstance();
targetChild.getMutator().addValue(theTarget, target);
cloneInto(nextValue, target, theIgnoreMissingFields);
}
}
theStack.remove(theElement);
}

View File

@ -144,7 +144,7 @@ public class SearchBuilder {
private ISearchResultDao mySearchResultDao;
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, IFulltextSearchSvc theSearchDao,
ISearchResultDao theSearchResultDao, BaseHapiFhirDao theDao, IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao) {
ISearchResultDao theSearchResultDao, BaseHapiFhirDao<?> theDao, IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao) {
myContext = theFhirContext;
myEntityManager = theEntityManager;
myPlatformTransactionManager = thePlatformTransactionManager;
@ -179,7 +179,7 @@ public class SearchBuilder {
IQueryParameterType rightValue = cp.getRightValue();
predicates.add(createCompositeParamPart(builder, from, right, rightValue));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
cq.where(builder.and(toArray(predicates)));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
@ -215,7 +215,8 @@ public class SearchBuilder {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateLastUpdatedForIndexedSearchParam(builder, from, predicates);
predicates.add(masterCodePredicate);
cq.where(builder.and(toArray(predicates)));
@ -237,8 +238,8 @@ public class SearchBuilder {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(from.get("myId").in(thePids));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
predicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, from));
createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
createPredicateLastUpdatedForResourceTable(builder, from, predicates);
cq.where(toArray(predicates));
@ -274,7 +275,8 @@ public class SearchBuilder {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(from.get("myLanguage").as(String.class).in(values));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
createPredicateLastUpdatedForResourceTable(builder, from, predicates);
predicates.add(builder.isNull(from.get("myDeleted")));
@ -366,7 +368,8 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateLastUpdatedForIndexedSearchParam(builder, from, predicates);
cq.where(builder.and(toArray(predicates)));
@ -391,7 +394,7 @@ public class SearchBuilder {
predicates.add(builder.not(builder.in(from.get("myId")).value(subQ)));
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.isNull(from.get("myDeleted")));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
cq.where(builder.and(toArray(predicates)));
@ -416,7 +419,7 @@ public class SearchBuilder {
subQ.where(path);
List<Predicate> predicates = new ArrayList<Predicate>();
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
predicates.add(builder.not(builder.in(from.get("myId")).value(subQ)));
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
@ -504,7 +507,8 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateLastUpdatedForIndexedSearchParam(builder, from, predicates);
cq.where(builder.and(toArray(predicates)));
@ -650,7 +654,8 @@ public class SearchBuilder {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(createResourceLinkPathPredicate(theParamName, builder, from));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(builder, cq, predicates, from.get("mySourceResourcePid").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("mySourceResourcePid").as(Long.class));
createPredicateLastUpdatedForResourceLink(builder, from, predicates);
cq.where(builder.and(toArray(predicates)));
@ -684,7 +689,9 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateLastUpdatedForIndexedSearchParam(builder, from, predicates);
cq.where(builder.and(toArray(predicates)));
@ -771,7 +778,7 @@ public class SearchBuilder {
andPredicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, defJoin));
}
doCreateIdPredicate(builder, cq, andPredicates, from.get("myResourceId").as(Long.class));
createPredicateResourceId(builder, cq, andPredicates, from.get("myResourceId").as(Long.class));
Predicate masterCodePredicate = builder.and(toArray(andPredicates));
cq.where(masterCodePredicate);
@ -821,7 +828,7 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
cq.where(builder.and(toArray(predicates)));
@ -906,7 +913,7 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
cq.where(builder.and(toArray(predicates)));
@ -996,6 +1003,28 @@ public class SearchBuilder {
}
}
private void createPredicateLastUpdatedForIndexedSearchParam(CriteriaBuilder builder, Root<? extends BaseResourceIndexedSearchParam> from, List<Predicate> predicates) {
DateRangeParam lastUpdated = myParams.getLastUpdatedAndRemove();
if (lastUpdated != null) {
From<BaseResourceIndexedSearchParam, ResourceTable> defJoin = from.join("myResource", JoinType.INNER);
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lastUpdated, builder, defJoin);
predicates.addAll(lastUpdatedPredicates);
}
}
private void createPredicateLastUpdatedForResourceLink(CriteriaBuilder builder, Root<ResourceLink> from, List<Predicate> predicates) {
DateRangeParam lastUpdated = myParams.getLastUpdatedAndRemove();
if (lastUpdated != null) {
From<BaseResourceIndexedSearchParam, ResourceTable> defJoin = from.join("mySourceResource", JoinType.INNER);
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lastUpdated, builder, defJoin);
predicates.addAll(lastUpdatedPredicates);
}
}
private void createPredicateLastUpdatedForResourceTable(CriteriaBuilder builder, Root<ResourceTable> from, List<Predicate> predicates) {
predicates.addAll(createLastUpdatedPredicates(myParams.getLastUpdatedAndRemove(), builder, from));
}
private Predicate createPredicateNumeric(CriteriaBuilder builder, IQueryParameterType params, ParamPrefixEnum cmpValue, BigDecimal valueValue, final Expression<BigDecimal> path,
String invalidMessageName, String theValueString) {
Predicate num;
@ -1039,6 +1068,26 @@ public class SearchBuilder {
return num;
}
private void createPredicateResourceId(CriteriaBuilder builder, CriteriaQuery<?> cq, List<Predicate> thePredicates, Expression<Long> theExpression) {
if (myParams.isPersistResults()) {
if (mySearchEntity.getTotalCount() > -1) {
Subquery<Long> subQ = cq.subquery(Long.class);
Root<SearchResult> subQfrom = subQ.from(SearchResult.class);
subQ.select(subQfrom.get("myResourcePid").as(Long.class));
Predicate subQname = builder.equal(subQfrom.get("mySearch"), mySearchEntity);
subQ.where(subQname);
thePredicates.add(theExpression.in(subQ));
}
} else {
if (myPids != null) {
thePredicates.add(theExpression.in(myPids));
}
}
}
private Predicate createPredicateString(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder,
From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theFrom) {
String rawSearchTerm;
@ -1242,24 +1291,6 @@ public class SearchBuilder {
createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates);
}
private void doCreateIdPredicate(CriteriaBuilder builder, CriteriaQuery<?> cq, List<Predicate> thePredicates, Expression<Long> theExpression) {
if (myParams.isPersistResults()) {
if (mySearchEntity.getTotalCount() > -1) {
Subquery<Long> subQ = cq.subquery(Long.class);
Root<SearchResult> subQfrom = subQ.from(SearchResult.class);
subQ.select(subQfrom.get("myResourcePid").as(Long.class));
Predicate subQname = builder.equal(subQfrom.get("mySearch"), mySearchEntity);
subQ.where(subQname);
thePredicates.add(theExpression.in(subQ));
}
} else {
if (myPids != null) {
thePredicates.add(theExpression.in(myPids));
}
}
}
public Set<Long> doGetPids() {
if (myParams.isPersistResults()) {
HashSet<Long> retVal = new HashSet<Long>();
@ -1355,7 +1386,7 @@ public class SearchBuilder {
cq.select(from.get("myId").as(Long.class));
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from);
doCreateIdPredicate(builder, cq, lastUpdatedPredicates, from.get("myId").as(Long.class));
createPredicateResourceId(builder, cq, lastUpdatedPredicates, from.get("myId").as(Long.class));
cq.where(SearchBuilder.toArray(lastUpdatedPredicates));
TypedQuery<Long> query = myEntityManager.createQuery(cq);
@ -1382,7 +1413,7 @@ public class SearchBuilder {
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<ResourceTable> from = cq.from(ResourceTable.class);
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
createSort(builder, from, theParams.getSort(), orders, predicates);
@ -1426,9 +1457,6 @@ public class SearchBuilder {
doInitializeSearch();
DateRangeParam lu = theParams.getLastUpdated();
if (lu != null && lu.isEmpty()) {
lu = null;
}
// Collection<Long> loadPids;
if (theParams.getEverythingMode() != null) {

View File

@ -115,10 +115,28 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return myIncludes;
}
/**
* Returns null if there is no last updated value
*/
public DateRangeParam getLastUpdated() {
if (myLastUpdated != null) {
if (myLastUpdated.isEmpty()) {
myLastUpdated = null;
}
}
return myLastUpdated;
}
/**
* Returns null if there is no last updated value, and removes the lastupdated
* value from this map
*/
public DateRangeParam getLastUpdatedAndRemove() {
DateRangeParam retVal = getLastUpdated();
myLastUpdated = null;
return retVal;
}
public RequestDetails getRequestDetails() {
return myRequestDetails;
}

View File

@ -4,6 +4,9 @@ import static org.junit.Assert.*;
import org.junit.Test;
import ca.uhn.fhir.model.api.annotation.Compartment;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.api.annotation.SearchParamDefinition;
import ca.uhn.fhir.model.dstu.resource.CarePlan;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.parser.DataFormatException;
@ -30,6 +33,15 @@ public class ModelScannerTest {
assertEquals(Patient.class, baseDef.getImplementingClass());
}
@Test
public void testResourceWithNoDef() {
try {
FhirContext.forDstu1().getResourceDefinition(NoResourceDef.class);
fail();
} catch (ConfigurationException e) {
assertEquals("Resource class[ca.uhn.fhir.context.ModelScannerTest$NoResourceDef] does not contain any valid HAPI-FHIR annotations", e.getMessage());
}
}
@Test
public void testScanExtensionTypes() throws DataFormatException {
@ -64,6 +76,50 @@ public class ModelScannerTest {
}
@Test
public void testSearchParamWithCompartmentForNonReferenceParam() {
try {
FhirContext.forDstu1().getResourceDefinition(CompartmentForNonReferenceParam.class);
fail();
} catch (ConfigurationException e) {
assertEquals("Search param foo provides compartment membershit but is not of type 'reference'", e.getMessage());
}
}
@Test
public void testSearchParamWithInvalidType() {
try {
FhirContext.forDstu1().getResourceDefinition(InvalidParamType.class);
fail();
} catch (ConfigurationException e) {
assertEquals("Search param foo has an invalid type: bar", e.getMessage());
}
}
@ResourceDef(name = "Patient")
public static class CompartmentForNonReferenceParam extends Patient {
private static final long serialVersionUID = 1L;
@SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "string", providesMembershipIn = { @Compartment(name = "Patient"), @Compartment(name = "Device") })
public static final String SP_TELECOM = "foo";
}
@ResourceDef(name = "Patient")
public static class InvalidParamType extends Patient {
private static final long serialVersionUID = 1L;
@SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "bar")
public static final String SP_TELECOM = "foo";
}
class NoResourceDef extends Patient {
private static final long serialVersionUID = 1L;
@SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "bar")
public static final String SP_TELECOM = "foo";
}
}

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>hapi-fhir-structures-dstu2.1</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.model.dstu2;
import static org.junit.Assert.*;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Patient;
public class CompartmentDstu2Test {
private static FhirContext ourCtx = FhirContext.forDstu2();
@Test
public void testMembership() {
Patient p1 = new Patient();
p1.setId("PID1");
Patient p2 = new Patient();
p2.setId("PID2");
Observation o = new Observation();
o.getSubject().setReference("Patient/PID1");
assertTrue(ourCtx.newTerser().isSourceInCompartmentForTarget("Patient", o, p1));
assertFalse(ourCtx.newTerser().isSourceInCompartmentForTarget("Patient", o, p2));
}
}

View File

@ -55,14 +55,11 @@ public class ${className} extends ca.uhn.fhir.model.${version}.resource.BaseReso
* Path: <b>$esc.html(${param.path})</b><br>
* </p>
*/
@SearchParamDefinition(name="${param.name}", path="${param.path}", description="${param.description}", type="${param.type}" #{if}($param.compositeOf.empty == false) , compositeOf={ #{foreach}($compositeOf in $param.compositeOf) "${compositeOf}"#{if}($foreach.hasNext), #{end}#{end} } #{end} )
#if ($param.compartments.size() > 0)
@Compartments(providesMembershipIn={
#foreach ($compartment in $param.compartments)
@Compartment(name="$compartment") #{if}($foreach.hasNext), #{end}
#end
})
#end
@SearchParamDefinition(name="${param.name}", path="${param.path}", description="${param.description}", type="${param.type}" #{if}($param.compositeOf.empty == false) , compositeOf={ #{foreach}($compositeOf in $param.compositeOf) "${compositeOf}"#{if}($foreach.hasNext), #{end}#{end} } #{end}
#{if} ($param.compartments.size() > 0), providesMembershipIn={
#{foreach} ($compartment in $param.compartments) @Compartment(name="$compartment") #{if}($foreach.hasNext), #{end}#{end}
}
#{end} )
public static final String $param.constantName = "${param.name}";
/**

View File

@ -235,6 +235,12 @@
($foo) where the response from the server was a raw resource instead
of a Parameters resource. Thanks to Andrew Michael Martin for reporting!
</action>
<action type="add">
JPA server applies _lastUpdated filter inline with other searches wherever possible
instead of applying this filter as a second query against the results of the
first query. This should improve performance when searching against large
datasets.
</action>
</release>
<release version="1.4" date="2016-02-04">
<action type="add">