Improve performance in parser when parsing large bundles

This commit is contained in:
jamesagnew 2016-04-23 13:54:16 -04:00
parent 3eb805fb0a
commit 514da88787
6 changed files with 56 additions and 80 deletions

View File

@ -230,6 +230,7 @@ public class JsonParser extends BaseParser implements IParser {
parseChildren(object, state); parseChildren(object, state);
state.endingElement();
state.endingElement(); state.endingElement();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -1109,6 +1110,7 @@ public class JsonParser extends BaseParser implements IParser {
parseBundleChildren(object, state); parseBundleChildren(object, state);
state.endingElement();
state.endingElement(); state.endingElement();
Bundle retVal = state.getObject(); Bundle retVal = state.getObject();
@ -1191,8 +1193,6 @@ public class JsonParser extends BaseParser implements IParser {
} }
private void parseChildren(JsonObject theObject, ParserState<?> theState) { private void parseChildren(JsonObject theObject, ParserState<?> theState) {
String elementId = null;
Set<String> keySet = theObject.keySet(); Set<String> keySet = theObject.keySet();
int allUnderscoreNames = 0; int allUnderscoreNames = 0;
@ -1201,23 +1201,6 @@ public class JsonParser extends BaseParser implements IParser {
for (String nextName : keySet) { for (String nextName : keySet) {
if ("resourceType".equals(nextName)) { if ("resourceType".equals(nextName)) {
continue; continue;
} else if ("id".equals(nextName)) {
if (theObject.isNull(nextName)) {
continue;
}
elementId = theObject.getString(nextName);
if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
continue;
}
} else if ("_id".equals(nextName)) {
if (theObject.isNull(nextName)) {
continue;
}
// _id is incorrect, but some early examples in the FHIR spec used it
if (theObject.get(nextName).getValueType() == ValueType.STRING) {
elementId = theObject.getString(nextName);
}
continue;
} else if ("extension".equals(nextName)) { } else if ("extension".equals(nextName)) {
JsonArray array = grabJsonArray(theObject, nextName, "extension"); JsonArray array = grabJsonArray(theObject, nextName, "extension");
parseExtension(theState, array, false); parseExtension(theState, array, false);
@ -1245,14 +1228,14 @@ public class JsonParser extends BaseParser implements IParser {
} }
if (elementId != null) { // if (elementId != null) {
IBase object = (IBase) theState.getObject(); // IBase object = (IBase) theState.getObject();
if (object instanceof IIdentifiableElement) { // if (object instanceof IIdentifiableElement) {
((IIdentifiableElement) object).setElementSpecificId(elementId); // ((IIdentifiableElement) object).setElementSpecificId(elementId);
} else if (object instanceof IBaseResource) { // } else if (object instanceof IBaseResource) {
((IBaseResource) object).getIdElement().setValue(elementId); // ((IBaseResource) object).getIdElement().setValue(elementId);
} // }
} // }
/* /*
* This happens if an element has an extension but no actual value. I.e. * This happens if an element has an extension but no actual value. I.e.
@ -1394,6 +1377,7 @@ public class JsonParser extends BaseParser implements IParser {
parseChildren(object, state); parseChildren(object, state);
state.endingElement();
state.endingElement(); state.endingElement();
return state.getObject(); return state.getObject();

View File

@ -149,14 +149,8 @@ class ParserState<T> {
myState.enteringNewElementExtension(theElem, theUrlAttr, theIsModifier); myState.enteringNewElementExtension(theElem, theUrlAttr, theIsModifier);
} }
@SuppressWarnings("unchecked")
public T getObject() { public T getObject() {
IBase retVal = myState.getCurrentElement(); return myObject;
return (T) retVal;
}
public boolean isComplete() {
return myObject != null;
} }
public boolean isPreResource() { public boolean isPreResource() {
@ -191,10 +185,16 @@ class ParserState<T> {
return newChildInstance; return newChildInstance;
} }
@SuppressWarnings("unchecked")
private void pop() { private void pop() {
myPreviousElement = myState.getCurrentElement(); myPreviousElement = myState.getCurrentElement();
myState = myState.myStack; if (myState.myStack != null) {
myState.wereBack(); myState = myState.myStack;
myState.wereBack();
} else {
myObject = (T) myState.getCurrentElement();
myState = null;
}
} }
private void push(BaseState theState) { private void push(BaseState theState) {
@ -250,7 +250,9 @@ class ParserState<T> {
* intended for embedded XHTML content * intended for embedded XHTML content
*/ */
public void xmlEvent(XMLEvent theNextEvent) { public void xmlEvent(XMLEvent theNextEvent) {
myState.xmlEvent(theNextEvent); if (myState != null) {
myState.xmlEvent(theNextEvent);
}
} }
static ParserState<Bundle> getPreAtomInstance(IParser theParser, FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException { static ParserState<Bundle> getPreAtomInstance(IParser theParser, FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
@ -772,7 +774,7 @@ class ParserState<T> {
@Override @Override
public void endingElement() throws DataFormatException { public void endingElement() throws DataFormatException {
// ignore pop();
} }
@Override @Override
@ -795,7 +797,6 @@ class ParserState<T> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public void wereBack() { public void wereBack() {
myObject = (T) myInstance;
/* /*
* Stitch together resource references * Stitch together resource references
@ -1609,9 +1610,6 @@ class ParserState<T> {
@Override @Override
public void endingElement() { public void endingElement() {
pop(); pop();
if (myState == null) {
myObject = (T) myInstance;
}
} }
@Override @Override
@ -1994,11 +1992,6 @@ class ParserState<T> {
super(theResourceType); super(theResourceType);
} }
@Override
public void endingElement() throws DataFormatException {
// ignore
}
@Override @Override
public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException { public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException {
if (!"feed".equals(theLocalPart)) { if (!"feed".equals(theLocalPart)) {
@ -2018,11 +2011,6 @@ class ParserState<T> {
super(theResourceType); super(theResourceType);
} }
@Override
public void endingElement() throws DataFormatException {
// ignore
}
@Override @Override
public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException { public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException {
if (!"Bundle".equals(theLocalPart)) { if (!"Bundle".equals(theLocalPart)) {
@ -2064,6 +2052,8 @@ class ParserState<T> {
@Override @Override
public void endingElement() throws DataFormatException { public void endingElement() throws DataFormatException {
// postProcess();
stitchBundleCrossReferences();
pop(); pop();
} }
@ -2143,6 +2133,10 @@ class ParserState<T> {
@Override @Override
public void wereBack() { public void wereBack() {
postProcess();
}
private void postProcess() {
if (myContext.hasDefaultTypeForProfile()) { if (myContext.hasDefaultTypeForProfile()) {
IBaseMetaType meta = myInstance.getMeta(); IBaseMetaType meta = myInstance.getMeta();
Class<? extends IBaseResource> wantedProfileType = null; Class<? extends IBaseResource> wantedProfileType = null;
@ -2217,8 +2211,13 @@ class ParserState<T> {
} }
}); });
populateTarget();
}
private void stitchBundleCrossReferences() {
final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName()); final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName());
if (bundle) { if (bundle) {
/* /*
* Stitch together resource references * Stitch together resource references
*/ */
@ -2258,8 +2257,6 @@ class ParserState<T> {
} }
} }
populateTarget();
} }
} }
@ -2307,9 +2304,6 @@ class ParserState<T> {
@Override @Override
public void wereBack() { public void wereBack() {
super.wereBack(); super.wereBack();
if (myEntry == null && myMutator == null) {
myObject = (T) getCurrentElement();
}
IResource nextResource = (IResource) getCurrentElement(); IResource nextResource = (IResource) getCurrentElement();
String version = ResourceMetadataKeyEnum.VERSION.get(nextResource); String version = ResourceMetadataKeyEnum.VERSION.get(nextResource);
@ -2353,9 +2347,6 @@ class ParserState<T> {
@Override @Override
public void wereBack() { public void wereBack() {
super.wereBack(); super.wereBack();
if (myTarget == null) {
myObject = (T) getCurrentElement();
}
if (getCurrentElement() instanceof IDomainResource) { if (getCurrentElement() instanceof IDomainResource) {
IDomainResource elem = (IDomainResource) getCurrentElement(); IDomainResource elem = (IDomainResource) getCurrentElement();
@ -2406,12 +2397,6 @@ class ParserState<T> {
return true; return true;
} }
@SuppressWarnings("unchecked")
@Override
public void wereBack() {
myObject = (T) myTagList;
}
} }
private class PrimitiveState extends BaseState { private class PrimitiveState extends BaseState {

View File

@ -242,6 +242,7 @@ public class XmlParser extends BaseParser implements IParser {
parserState.attributeValue(name, elem.getValue()); parserState.attributeValue(name, elem.getValue());
break; break;
} }
case XMLStreamConstants.END_DOCUMENT:
case XMLStreamConstants.END_ELEMENT: { case XMLStreamConstants.END_ELEMENT: {
if (!heldComments.isEmpty()) { if (!heldComments.isEmpty()) {
for (String next : heldComments) { for (String next : heldComments) {
@ -250,9 +251,9 @@ public class XmlParser extends BaseParser implements IParser {
heldComments.clear(); heldComments.clear();
} }
parserState.endingElement(); parserState.endingElement();
if (parserState.isComplete()) { // if (parserState.isComplete()) {
return parserState.getObject(); // return parserState.getObject();
} // }
break; break;
} }
case XMLStreamConstants.CHARACTERS: { case XMLStreamConstants.CHARACTERS: {
@ -273,7 +274,7 @@ public class XmlParser extends BaseParser implements IParser {
throw new DataFormatException("DataFormatException at [" + nextEvent.getLocation().toString() + "]: " + e.getMessage(), e); throw new DataFormatException("DataFormatException at [" + nextEvent.getLocation().toString() + "]: " + e.getMessage(), e);
} }
} }
return null; return parserState.getObject();
} catch (XMLStreamException e) { } catch (XMLStreamException e) {
throw new DataFormatException(e); throw new DataFormatException(e);
} }

View File

@ -681,10 +681,6 @@ public class JsonParserTest {
ListResource parsed = ourCtx.newJsonParser().parseResource(ListResource.class, enc); ListResource parsed = ourCtx.newJsonParser().parseResource(ListResource.class, enc);
assertEquals(Patient.class, parsed.getEntryFirstRep().getItem().getResource().getClass()); assertEquals(Patient.class, parsed.getEntryFirstRep().getItem().getResource().getClass());
enc = enc.replace("\"id\"", "\"_id\"");
parsed = ourCtx.newJsonParser().parseResource(ListResource.class, enc);
assertEquals(Patient.class, parsed.getEntryFirstRep().getItem().getResource().getClass());
} }
@Test @Test

View File

@ -645,11 +645,7 @@ public class JsonParserHl7OrgDstu2Test {
List_ parsed = ourCtx.newJsonParser().parseResource(List_.class,enc); List_ parsed = ourCtx.newJsonParser().parseResource(List_.class,enc);
assertEquals(Patient.class, parsed.getEntry().get(0).getItem().getResource().getClass()); assertEquals(Patient.class, parsed.getEntry().get(0).getItem().getResource().getClass());
}
enc = enc.replace("\"id\"", "\"_id\"");
parsed = ourCtx.newJsonParser().parseResource(List_.class,enc);
assertEquals(Patient.class, parsed.getEntry().get(0).getItem().getResource().getClass());
}
@Test @Test
public void testEncodeInvalidChildGoodException() { public void testEncodeInvalidChildGoodException() {

View File

@ -22,9 +22,23 @@
</action> </action>
<action type="add" issue="346"> <action type="add" issue="346">
Server now respects the parameter <![CDATA[<code>_format=application/xml+fhir"</code>]]> Server now respects the parameter <![CDATA[<code>_format=application/xml+fhir"</code>]]>
which is technically invalid since the + should be escaped, but is likely to be used. which is technically invalid since the + should be escaped, but is likely to be used. Also,
a parameter of <![CDATA[<code>_format=html</code>]]> can now be used, which
forces SyntaxHighlightingInterceptor to use HTML even
if the headers wouldn't otherwise trigger it.
Thanks to Jim Steel for reporting! Thanks to Jim Steel for reporting!
</action> </action>
<action type="fix">
Improve performance when parsing large bundles by fixing a loop over all of the
entries inthe bundle to stitch together cross-references, which was happening once
per entry instead of once overall. Thanks to Erick on the HAPI FHIR Google Group for
noticing that this was an issue!
</action>
<action type="remove">
JSON parser no longer allows the resource ID to be specified in an element called "_id"
(the correct one is "id"). Previously _id was allowed because some early FHIR examples
used that form, but this was never actually valid so it is now being removed.
</action>
</release> </release>
<release version="1.5" date="2016-04-20"> <release version="1.5" date="2016-04-20">
<action type="fix" issue="339"> <action type="fix" issue="339">