Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/json performance #1489

Merged
merged 7 commits into from Sep 20, 2019
303 changes: 204 additions & 99 deletions hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java
Expand Up @@ -27,6 +27,7 @@
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.UrlUtil;

import com.google.common.base.Charsets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
Expand Down Expand Up @@ -67,6 +68,47 @@ public abstract class BaseParser implements IParser {
private boolean mySuppressNarratives;
private Set<String> myDontStripVersionsFromReferencesAtPaths;

private Map<Key, List<CompositeChildElement>> compositeChildrenCache = new HashMap<>();

private static class Key {
private final BaseRuntimeElementCompositeDefinition<?> resDef;
private final boolean theContainedResource;
private final CompositeChildElement theParent;
private final EncodeContext theEncodeContext;

public Key(BaseRuntimeElementCompositeDefinition<?> resDef, final boolean theContainedResource, final CompositeChildElement theParent, EncodeContext theEncodeContext) {
this.resDef = resDef;
this.theContainedResource = theContainedResource;
this.theParent = theParent;
this.theEncodeContext = theEncodeContext;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((resDef == null) ? 0 : resDef.hashCode());
result = prime * result + (theContainedResource ? 1231 : 1237);
result = prime * result + ((theParent == null) ? 0 : theParent.hashCode());
result = prime * result + ((theEncodeContext == null) ? 0 : theEncodeContext.hashCode());
return result;
}

@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Key) {
final Key that = (Key) obj;
return Objects.equals(this.resDef, that.resDef) &&
this.theContainedResource == that.theContainedResource &&
Objects.equals(this.theParent, that.theParent) &&
Objects.equals(this.theEncodeContext, that.theEncodeContext);
}
return false;
}
}

/**
* Constructor
*/
Expand Down Expand Up @@ -128,114 +170,41 @@ public IParser setEncodeElements(Set<String> theEncodeElements) {
}

protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final CompositeChildElement theParent, EncodeContext theEncodeContext) {

BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass());
final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();

return new Iterable<BaseParser.CompositeChildElement>() {

@Override
public Iterator<CompositeChildElement> iterator() {
return compositeChildrenCache.computeIfAbsent(new Key(elementDef, theContainedResource, theParent, theEncodeContext), (k) -> {

final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
final List<CompositeChildElement> result = new ArrayList<>(children.size());

return new Iterator<CompositeChildElement>() {
private Iterator<? extends BaseRuntimeChildDefinition> myChildrenIter;
private Boolean myHasNext = null;
private CompositeChildElement myNext;
for(final BaseRuntimeChildDefinition child: children) {
CompositeChildElement myNext = new CompositeChildElement(theParent, child, theEncodeContext);

/**
* Constructor
*/ {
myChildrenIter = children.iterator();
}

@Override
public boolean hasNext() {
if (myHasNext != null) {
return myHasNext;
}

myNext = null;
do {
if (myChildrenIter.hasNext() == false) {
myHasNext = Boolean.FALSE;
return false;
}

myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theEncodeContext);

/*
* There are lots of reasons we might skip encoding a particular child
*/
if (myNext.getDef().getElementName().equals("id")) {
myNext = null;
} else if (!myNext.shouldBeEncoded(theContainedResource)) {
myNext = null;
} else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) {
if (isSuppressNarratives() || isSummaryMode()) {
myNext = null;
} else if (theContainedResource) {
myNext = null;
}
} else if (myNext.getDef() instanceof RuntimeChildContainedResources) {
if (theContainedResource) {
myNext = null;
}
}
} while (myNext == null);

myHasNext = true;
return true;
}

@Override
public CompositeChildElement next() {
if (myHasNext == null) {
if (!hasNext()) {
throw new IllegalStateException();
}
}
CompositeChildElement retVal = myNext;
myNext = null;
myHasNext = null;
return retVal;
/*
* There are lots of reasons we might skip encoding a particular child
*/
if (myNext.getDef().getElementName().equals("id")) {
continue;
} else if (!myNext.shouldBeEncoded(theContainedResource)) {
continue;
} else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) {
if (isSuppressNarratives() || isSummaryMode()) {
continue;
} else if (theContainedResource) {
continue;
}

@Override
public void remove() {
throw new UnsupportedOperationException();
} else if (myNext.getDef() instanceof RuntimeChildContainedResources) {
if (theContainedResource) {
continue;
}
};
}
result.add(myNext);
}
};
return result;
});
}

private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, IBaseResource theTarget) {

if (theTarget instanceof IResource) {
List<? extends IResource> containedResources = ((IResource) theTarget).getContained().getContainedResources();
for (IResource next : containedResources) {
String nextId = next.getId().getValue();
if (StringUtils.isNotBlank(nextId)) {
if (!nextId.startsWith("#")) {
nextId = '#' + nextId;
}
theContained.getExistingIdToContainedResource().put(nextId, next);
}
}
} else if (theTarget instanceof IDomainResource) {
List<? extends IAnyResource> containedResources = ((IDomainResource) theTarget).getContained();
for (IAnyResource next : containedResources) {
String nextId = next.getIdElement().getValue();
if (StringUtils.isNotBlank(nextId)) {
if (!nextId.startsWith("#")) {
nextId = '#' + nextId;
}
theContained.getExistingIdToContainedResource().put(nextId, next);
}
}
}

List<IBaseReference> allReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
List<IBaseReference> allReferences = getAllBaseReferences(theResource);
for (IBaseReference next : allReferences) {
IBaseResource resource = next.getResource();
if (resource == null && next.getReferenceElement().isLocal()) {
Expand Down Expand Up @@ -274,12 +243,117 @@ private void containResourcesForEncoding(ContainedResources theContained, IBaseR

protected void containResourcesForEncoding(IBaseResource theResource) {
ContainedResources contained = new ContainedResources();

if (theResource instanceof IResource) {
List<? extends IResource> containedResources = ((IResource) theResource).getContained().getContainedResources();
for (IResource next : containedResources) {
String nextId = next.getId().getValue();
if (StringUtils.isNotBlank(nextId)) {
if (!nextId.startsWith("#")) {
nextId = '#' + nextId;
}
contained.getExistingIdToContainedResource().put(nextId, next);
}
}
} else if (theResource instanceof IDomainResource) {
List<? extends IAnyResource> containedResources = ((IDomainResource) theResource).getContained();
for (IAnyResource next : containedResources) {
String nextId = next.getIdElement().getValue();
if (StringUtils.isNotBlank(nextId)) {
if (!nextId.startsWith("#")) {
nextId = '#' + nextId;
}
contained.getExistingIdToContainedResource().put(nextId, next);
}
}
}

containResourcesForEncoding(contained, theResource, theResource);
contained.assignIdsToContainedResources();
myContainedResources = contained;

}

protected List<IBaseReference> getAllBaseReferences(IBaseResource theResource){
final ArrayList<IBaseReference> retVal = new ArrayList<IBaseReference>();
findBaseReferences(retVal, theResource, myContext.getResourceDefinition(theResource));
return retVal;
}

/**
* A customised traversal of the tree to find the 'top level' base references. Nested references are found via the recursive traversal
* of contained resources.
*/
protected void findBaseReferences(List<IBaseReference> allElements, IBase theElement, BaseRuntimeElementDefinition<?> theDefinition) {
if (theElement instanceof IBaseReference) {
allElements.add((IBaseReference) theElement);
}

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:
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
if (nextValue instanceof IBaseReference) {
allElements.add((IBaseReference) nextValue);
}
} else {
findBaseReferences(allElements, nextValue, childElementDef);
}
}
}
}
break;
}
case CONTAINED_RESOURCES:
// skip contained resources when looking for resources to contain
break;
case CONTAINED_RESOURCE_LIST:
case EXTENSION_DECLARED:
case UNDECL_EXT: {
throw new IllegalStateException("state should not happen: " + def.getChildType());
}
}
}

private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) {
IIdType ref = theRef.getReferenceElement();
if (isBlank(ref.getIdPart())) {
Expand Down Expand Up @@ -1193,6 +1267,37 @@ public boolean shouldBeEncoded(boolean theContainedResource) {

return retVal;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((myDef == null) ? 0 : myDef.hashCode());
result = prime * result + ((myParent == null) ? 0 : myParent.hashCode());
result = prime * result + ((myResDef == null) ? 0 : myResDef.hashCode());
result = prime * result + ((myEncodeContext == null) ? 0 : myEncodeContext.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;

if (obj instanceof CompositeChildElement) {
final CompositeChildElement that = (CompositeChildElement) obj;
return Objects.equals(this.getEnclosingInstance(), that.getEnclosingInstance()) &&
Objects.equals(this.myDef, that.myDef) &&
Objects.equals(this.myParent, that.myParent) &&
Objects.equals(this.myResDef, that.myResDef) &&
Objects.equals(this.myEncodeContext, that.myEncodeContext);
}
return false;
}

private BaseParser getEnclosingInstance() {
return BaseParser.this;
}
}

protected class EncodeContextPath {
Expand Down
16 changes: 13 additions & 3 deletions hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
Expand Up @@ -224,13 +224,24 @@ private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDe
}
case PRIMITIVE_DATATYPE: {
final IPrimitiveType<?> value = (IPrimitiveType<?>) theNextValue;
if (isBlank(value.getValueAsString())) {
final String valueStr = value.getValueAsString();
if (isBlank(valueStr)) {
if (theForceEmpty) {
theEventWriter.writeNull();
}
break;
}

// check for the common case first - String value types
if (value.getValue() instanceof String) {
if (theChildName != null) {
theEventWriter.write(theChildName, valueStr);
} else {
theEventWriter.write(valueStr);
}
break;
}

if (value instanceof IBaseIntegerDatatype) {
if (theChildName != null) {
write(theEventWriter, theChildName, ((IBaseIntegerDatatype) value).getValue());
Expand Down Expand Up @@ -262,7 +273,6 @@ public String toString() {
}
}
} else {
String valueStr = value.getValueAsString();
if (theChildName != null) {
write(theEventWriter, theChildName, valueStr);
} else {
Expand Down Expand Up @@ -459,7 +469,7 @@ private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinit
if (nextValue instanceof IBaseHasModifierExtensions) {
IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) nextValue;
List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension();
force |= addToHeldExtensions(valueIdx, ext, extensions, true, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement);
force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement);
}
}
if (nextValue.hasFormatComment()) {
Expand Down