From d20b7e2c94fa26bcf9448ea60ff9558b828429ef Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 7 Jul 2015 17:27:10 -0400 Subject: [PATCH] A few cleanups to allow example resources to commit cleanly --- .../java/ca/uhn/fhir/parser/BaseParser.java | 5 +- .../java/ca/uhn/fhir/parser/ParserState.java | 2 +- .../java/ca/uhn/fhir/util/FhirTerser.java | 154 +++++++++--------- .../hl7/fhir/instance/model/api/IIdType.java | 3 + hapi-fhir-examples-uploader/.gitignore | 1 + hapi-fhir-examples-uploader/pom.xml | 128 +++++++++++++++ .../ca/uhn/fhir/exampleuploader/Uploader.java | 149 +++++++++++++++++ .../src/main/resources/logback-test.xml | 30 ++++ .../jpa/dao/SearchParamExtractorDstu2.java | 53 ++++-- .../jpa/dao/FhirResourceDaoDstu2Test.java | 34 ++++ .../java/ca/uhn/fhirtest/UhnFhirTestApp.java | 5 +- .../test/java/ca/uhn/fhir/context/Perf.java | 34 ++++ .../ca/uhn/fhir/model/primitive/IdDtTest.java | 1 + .../ca/uhn/fhir/parser/XmlParserTest.java | 22 +++ src/changes/changes.xml | 4 + 15 files changed, 529 insertions(+), 96 deletions(-) create mode 100644 hapi-fhir-examples-uploader/.gitignore create mode 100644 hapi-fhir-examples-uploader/pom.xml create mode 100644 hapi-fhir-examples-uploader/src/main/java/ca/uhn/fhir/exampleuploader/Uploader.java create mode 100644 hapi-fhir-examples-uploader/src/main/resources/logback-test.xml create mode 100644 hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/Perf.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 0065e3e5f5d..6067ee7ef02 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -87,7 +87,6 @@ public abstract class BaseParser implements IParser { } private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, IBaseResource theTarget) { - Set allIds = new HashSet(); Map existingIdToContainedResource = null; @@ -131,6 +130,10 @@ public abstract class BaseParser implements IParser { IBaseResource resource = next.getResource(); if (resource != null) { if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) { + if (theContained.getResourceId(resource) != null) { + // Prevent infinite recursion if there are circular loops in the contained resources + continue; + } theContained.addContained(resource); } else { continue; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index dc3e454c597..f6eeaf5f710 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -1684,7 +1684,7 @@ class ParserState { @Override public void endingElement() throws DataFormatException { if (myExtension.getValue() != null && myExtension.getExtension().size() > 0) { - throw new DataFormatException("Extension must not have both a value and other contained extensions"); + throw new DataFormatException("Extension (URL='" + myExtension.getUrl() + "') must not have both a value and other contained extensions"); } pop(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java index 37ec4c76780..9b979ac09aa 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.TreeSet; @@ -68,29 +69,26 @@ public class FhirTerser { } /** - * Returns a list containing all child elements (including the resource itself) which are non-empty and are - * either of the exact type specified, or are a subclass of that type. + * Returns a list containing all child elements (including the resource itself) which are non-empty and are either of the exact type specified, or are a subclass of that type. *

- * For example, specifying a type of {@link StringDt} would return all non-empty string instances within the - * message. Specifying a type of {@link IResource} would return the resource itself, as well as any contained - * resources. + * For example, specifying a type of {@link StringDt} would return all non-empty string instances within the message. Specifying a type of {@link IResource} would return the resource itself, as + * well as any contained resources. *

*

- * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, - * but will not descend into linked resources (e.g. {@link BaseResourceReferenceDt#getResource()}) or embedded - * resources (e.g. Bundle.entry.resource) + * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g. + * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) *

* * @param theResource - * The resource instance to search. Must not be null. + * The resource instance to search. Must not be null. * @param theType - * The type to search for. Must not be null. + * The type to search for. Must not be null. * @return Returns a list of all matching elements */ public List getAllPopulatedChildElementsOfType(IBaseResource theResource, final Class theType) { final ArrayList retVal = new ArrayList(); BaseRuntimeElementCompositeDefinition def = myContext.getResourceDefinition(theResource); - visit(theResource, null, null, def, new IModelVisitor() { + visit(new IdentityHashMap(), theResource, null, null, def, new IModelVisitor() { @SuppressWarnings("unchecked") @Override public void acceptElement(IBase theElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition) { @@ -105,8 +103,8 @@ public class FhirTerser { @SuppressWarnings("unchecked") @Override - public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition, - ExtensionDt theNextExt) { + public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, + BaseRuntimeElementDefinition theDefinition, ExtensionDt theNextExt) { if (theType.isAssignableFrom(theNextExt.getClass())) { retVal.add((T) theNextExt); } @@ -118,32 +116,32 @@ public class FhirTerser { return retVal; } - public List getAllResourceReferences(final IBaseResource theResource) { - final ArrayList retVal = new ArrayList(); - BaseRuntimeElementCompositeDefinition def = myContext.getResourceDefinition(theResource); - visit(theResource, null, null, def, new IModelVisitor() { - @Override - public void acceptElement(IBase theElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition) { - if (theElement == null || theElement.isEmpty()) { - return; - } - if (IBaseReference.class.isAssignableFrom(theElement.getClass())) { - retVal.add(new ResourceReferenceInfo(myContext, theResource, thePathToElement, (IBaseReference)theElement)); - } - } + public List getAllResourceReferences(final IBaseResource theResource) { + final ArrayList retVal = new ArrayList(); + BaseRuntimeElementCompositeDefinition def = myContext.getResourceDefinition(theResource); + visit(new IdentityHashMap(),theResource, null, null, def, new IModelVisitor() { + @Override + public void acceptElement(IBase theElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition) { + if (theElement == null || theElement.isEmpty()) { + return; + } + if (IBaseReference.class.isAssignableFrom(theElement.getClass())) { + retVal.add(new ResourceReferenceInfo(myContext, theResource, thePathToElement, (IBaseReference) theElement)); + } + } - @Override - public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition, - ExtensionDt theNextExt) { - if (theNextExt.getValue() != null && BaseResourceReferenceDt.class.isAssignableFrom(theNextExt.getValue().getClass())) { - retVal.add(new ResourceReferenceInfo(myContext, theResource, thePathToElement, (BaseResourceReferenceDt)theNextExt.getValue())); - } - } - }); - return retVal; - } + @Override + public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, + BaseRuntimeElementDefinition theDefinition, ExtensionDt theNextExt) { + if (theNextExt.getValue() != null && BaseResourceReferenceDt.class.isAssignableFrom(theNextExt.getValue().getClass())) { + retVal.add(new ResourceReferenceInfo(myContext, theResource, thePathToElement, (BaseResourceReferenceDt) theNextExt.getValue())); + } + } + }); + return retVal; + } - private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition theCurrentDef, List theSubList) { + private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition theCurrentDef, List theSubList) { BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0)); if (theSubList.size() == 1) { @@ -214,7 +212,7 @@ public class FhirTerser { public List getValues(IBaseResource theResource, String thePath) { Class wantedClass = Object.class; - + return getValues(theResource, thePath, wantedClass); } @@ -233,24 +231,30 @@ public class FhirTerser { return getValues(currentDef, currentObj, subList, theWantedClass); } - private List addNameToList(List theCurrentList, BaseRuntimeChildDefinition theChildDefinition) { - if (theChildDefinition == null) - return null; - if (theCurrentList== null || theCurrentList.isEmpty()) - return new ArrayList(Arrays.asList(theChildDefinition.getElementName())); - List newList = new ArrayList(theCurrentList); - newList.add(theChildDefinition.getElementName()); - return newList; - } + private List addNameToList(List theCurrentList, BaseRuntimeChildDefinition theChildDefinition) { + if (theChildDefinition == null) + return null; + if (theCurrentList == null || theCurrentList.isEmpty()) + return new ArrayList(Arrays.asList(theChildDefinition.getElementName())); + List newList = new ArrayList(theCurrentList); + newList.add(theChildDefinition.getElementName()); + return newList; + } - private void visit(IBase theElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition, IModelVisitor theCallback) { - List pathToElement = addNameToList(thePathToElement, theChildDefinition); + private void visit(IdentityHashMap theStack, IBase theElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, + BaseRuntimeElementDefinition theDefinition, IModelVisitor theCallback) { + List pathToElement = addNameToList(thePathToElement, theChildDefinition); + + if (theStack.put(theElement, theElement) != null) { + return; + } + theCallback.acceptElement(theElement, pathToElement, theChildDefinition, theDefinition); addUndeclaredExtensions(theElement, theDefinition, theChildDefinition, theCallback); - // if (theElement.isEmpty()) { - // return; - // } + if (theElement.isEmpty()) { + return; + } switch (theDefinition.getChildType()) { case ID_DATATYPE: @@ -265,7 +269,7 @@ public class FhirTerser { IBaseResource theResource = resRefDt.getResource(); if (theResource.getIdElement() == null || theResource.getIdElement().isEmpty() || theResource.getIdElement().isLocal()) { BaseRuntimeElementCompositeDefinition def = myContext.getResourceDefinition(theResource); - visit(theResource, pathToElement, null, def, theCallback); + visit(theStack, theResource, pathToElement, null, def, theCallback); } } break; @@ -287,14 +291,14 @@ public class FhirTerser { childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass()); if (childElementDef == null) { - childElementDef = myContext.getElementDefinition(nextValue.getClass()); + childElementDef = myContext.getElementDefinition(nextValue.getClass()); } if (nextChild instanceof RuntimeChildDirectResource) { // Don't descend into embedded resources theCallback.acceptElement(nextValue, null, nextChild, childElementDef); } else { - visit(nextValue, pathToElement, nextChild, childElementDef, theCallback); + visit(theStack, nextValue, pathToElement, nextChild, childElementDef, theCallback); } } } @@ -305,7 +309,7 @@ public class FhirTerser { BaseContainedDt value = (BaseContainedDt) theElement; for (IResource next : value.getContainedResources()) { BaseRuntimeElementCompositeDefinition def = myContext.getResourceDefinition(next); - visit(next, pathToElement, null, def, theCallback); + visit(theStack, next, pathToElement, null, def, theCallback); } break; } @@ -316,21 +320,25 @@ public class FhirTerser { case CONTAINED_RESOURCE_LIST: if (theElement != null) { BaseRuntimeElementDefinition def = myContext.getElementDefinition(theElement.getClass()); - visit(theElement, pathToElement, null, def, theCallback); + visit(theStack, theElement, pathToElement, null, def, theCallback); } break; } + + theStack.remove(theElement); + } - private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition, IModelVisitor2 theCallback, List theContainingElementPath, List theChildDefinitionPath, - List> theElementDefinitionPath) { + private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition, IModelVisitor2 theCallback, List theContainingElementPath, + List theChildDefinitionPath, List> theElementDefinitionPath) { if (theChildDefinition != null) { theChildDefinitionPath.add(theChildDefinition); } theContainingElementPath.add(theElement); theElementDefinitionPath.add(theDefinition); - theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath), Collections.unmodifiableList(theElementDefinitionPath)); + theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath), + Collections.unmodifiableList(theElementDefinitionPath)); /* * Visit undeclared extensions @@ -405,7 +413,8 @@ public class FhirTerser { theContainingElementPath.add(nextValue); theChildDefinitionPath.add(nextChild); theElementDefinitionPath.add(myContext.getElementDefinition(nextValue.getClass())); - theCallback.acceptElement(nextValue, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath), Collections.unmodifiableList(theElementDefinitionPath)); + theCallback.acceptElement(nextValue, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath), + Collections.unmodifiableList(theElementDefinitionPath)); theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1); theContainingElementPath.remove(theContainingElementPath.size() - 1); theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1); @@ -449,19 +458,18 @@ public class FhirTerser { * Visit all elements in a given resource * *

- * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, - * but will not descend into linked resources (e.g. {@link BaseResourceReferenceDt#getResource()}) or embedded - * resources (e.g. Bundle.entry.resource) + * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g. + * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) *

* * @param theResource - * The resource to visit + * The resource to visit * @param theVisitor - * The visitor + * The visitor */ public void visit(IBaseResource theResource, IModelVisitor theVisitor) { BaseRuntimeElementCompositeDefinition def = myContext.getResourceDefinition(theResource); - visit(theResource, null, null, def, theVisitor); + visit(new IdentityHashMap(), theResource, null, null, def, theVisitor); } /** @@ -470,15 +478,14 @@ public class FhirTerser { * THIS ALTERNATE METHOD IS STILL EXPERIMENTAL * *

- * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, - * but will not descend into linked resources (e.g. {@link BaseResourceReferenceDt#getResource()}) or embedded - * resources (e.g. Bundle.entry.resource) + * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g. + * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) *

* * @param theResource - * The resource to visit + * The resource to visit * @param theVisitor - * The visitor + * The visitor */ void visit(IBaseResource theResource, IModelVisitor2 theVisitor) { BaseRuntimeElementCompositeDefinition def = myContext.getResourceDefinition(theResource); @@ -487,7 +494,7 @@ public class FhirTerser { public Object getSingleValueOrNull(IBase theTarget, String thePath) { Class wantedType = Object.class; - + return getSingleValueOrNull(theTarget, thePath, wantedType); } @@ -512,5 +519,4 @@ public class FhirTerser { } } - } diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IIdType.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IIdType.java index 1151419d3fd..0f677f30e9f 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IIdType.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IIdType.java @@ -87,6 +87,9 @@ public interface IIdType { IIdType withServerBase(String theServerBase, String theResourceName); + /** + * Returns true if this ID contains an absolute URL (in other words, a URL starting with "http://" or "https://" + */ boolean isAbsolute(); boolean isIdPartValidLong(); diff --git a/hapi-fhir-examples-uploader/.gitignore b/hapi-fhir-examples-uploader/.gitignore new file mode 100644 index 00000000000..84c048a73cc --- /dev/null +++ b/hapi-fhir-examples-uploader/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/hapi-fhir-examples-uploader/pom.xml b/hapi-fhir-examples-uploader/pom.xml new file mode 100644 index 00000000000..459f934e3ad --- /dev/null +++ b/hapi-fhir-examples-uploader/pom.xml @@ -0,0 +1,128 @@ + + 4.0.0 + + + + ca.uhn.hapi.fhir + hapi-fhir + 1.1-SNAPSHOT + ../pom.xml + + + ca.uhn.hapi.example + hapi-fhir-examples-uploader + 1.1-SNAPSHOT + jar + + HAPI FHIR - Examples Uploader + + + + + + ca.uhn.hapi.fhir + hapi-fhir-base + 1.1-SNAPSHOT + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu2 + 1.1-SNAPSHOT + + + + ch.qos.logback + logback-classic + 1.1.2 + + + javax.servlet + javax.servlet-api + 3.0.1 + provided + + + + + + + + + hapi-fhir-jpaserver-example + + + + + + org.eclipse.jetty + jetty-maven-plugin + 9.1.1.v20140108 + + + /hapi-fhir-jpaserver-example + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + + org.apache.maven.plugins + maven-war-plugin + + + + ca.uhn.hapi.fhir + hapi-fhir-testpage-overlay + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + diff --git a/hapi-fhir-examples-uploader/src/main/java/ca/uhn/fhir/exampleuploader/Uploader.java b/hapi-fhir-examples-uploader/src/main/java/ca/uhn/fhir/exampleuploader/Uploader.java new file mode 100644 index 00000000000..37993cd9b90 --- /dev/null +++ b/hapi-fhir-examples-uploader/src/main/java/ca/uhn/fhir/exampleuploader/Uploader.java @@ -0,0 +1,149 @@ +package ca.uhn.fhir.exampleuploader; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; +import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryTransaction; +import ca.uhn.fhir.model.dstu2.resource.SearchParameter; +import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.util.ResourceReferenceInfo; + +public class Uploader { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Uploader.class); + + public static void main(String[] args) throws Exception { + ourLog.info("Starting..."); + + FhirContext ctx = FhirContext.forDstu2(); + + HttpGet get = new HttpGet("http://hl7.org/fhir/2015May/examples-json.zip"); + CloseableHttpClient client = HttpClientBuilder.create().build(); + CloseableHttpResponse result = client.execute(get); + byte[] bytes = IOUtils.toByteArray(result.getEntity().getContent()); + IOUtils.closeQuietly(result.getEntity().getContent()); + + ourLog.info("Loaded examples ({} bytes)", bytes.length); + + ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bytes)); + byte[] buffer = new byte[2048]; + + Bundle bundle = new Bundle(); + + while (true) { + ZipEntry nextEntry = zis.getNextEntry(); + if (nextEntry == null) { + break; + } + + int len = 0; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + while ((len = zis.read(buffer)) > 0) { + bos.write(buffer, 0, len); + } + byte[] exampleBytes = bos.toByteArray(); + String exampleString = new String(exampleBytes, "UTF-8"); + + IBaseResource parsed; + try { + parsed = ctx.newJsonParser().parseResource(exampleString); + } catch (DataFormatException e) { + ourLog.info("FAILED to parse example {}", nextEntry.getName(), e); + continue; + } + ourLog.info("Found example {} - {} - {} chars", nextEntry.getName(), parsed.getClass().getSimpleName(), exampleString.length()); + + if (parsed instanceof Bundle) { + Bundle b = (Bundle) parsed; + for (Entry nextEntry1 : b.getEntry()) { + if (nextEntry1.getResource() == null) { + continue; + } + if (nextEntry1.getResource() instanceof Bundle) { + continue; + } + if (nextEntry1.getResource() instanceof SearchParameter) { + continue; + } + bundle.addEntry().setTransaction(new EntryTransaction().setMethod(HTTPVerbEnum.POST)).setResource(nextEntry1.getResource()); + } + } else { + if (parsed instanceof SearchParameter) { + continue; + } + bundle.addEntry().setTransaction(new EntryTransaction().setMethod(HTTPVerbEnum.POST)).setResource((IResource) parsed); + } + + } + + Set ids = new HashSet(); + for (int i = 0; i < bundle.getEntry().size(); i++) { + Entry next = bundle.getEntry().get(i); + if (next.getResource().getId().getIdPart() != null) { + String nextId = next.getResource().getResourceName() + '/' + next.getResource().getId().getIdPart(); + if (!ids.add(nextId)) { + ourLog.info("Discarding duplicate resource with ID: " + nextId); + bundle.getEntry().remove(i); + i--; + } + } + } + + int goodRefs = 0; + for (Entry next : bundle.getEntry()) { + List refs = ctx.newTerser().getAllResourceReferences(next.getResource()); + for (ResourceReferenceInfo nextRef : refs) { +// if (nextRef.getResourceReference().getReferenceElement().isAbsolute()) { +// ourLog.info("Discarding absolute reference: {}", nextRef.getResourceReference().getReferenceElement().getValue()); +// nextRef.getResourceReference().getReferenceElement().setValue(null); +// } + nextRef.getResourceReference().getReferenceElement().setValue(nextRef.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue()); + String value = nextRef.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue(); + if (!ids.contains(value) && !nextRef.getResourceReference().getReferenceElement().isLocal()) { + ourLog.info("Discarding unknown reference: {}", value); + nextRef.getResourceReference().getReferenceElement().setValue(null); + } else { + goodRefs++; + } + } + } + +// for (Entry next : bundle.getEntry()) { +// if (next.getResource().getId().hasIdPart() && Character.isLetter(next.getResource().getId().getIdPart().charAt(0))) { +// next.getTransaction().setUrl(next.getResource().getResourceName() + '/' + next.getResource().getId().getIdPart()); +// next.getTransaction().setMethod(HTTPVerbEnum.PUT); +// } +// } + + ourLog.info("{} good references", goodRefs); + + String encoded = ctx.newJsonParser().encodeResourceToString(bundle); + ourLog.info("Final bundle: {} entries", bundle.getEntry().size()); + ourLog.info("Final bundle: {} chars", encoded.length()); + + IGenericClient fhirClient = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); + fhirClient.transaction().withBundle(bundle).execute(); + + } + +} diff --git a/hapi-fhir-examples-uploader/src/main/resources/logback-test.xml b/hapi-fhir-examples-uploader/src/main/resources/logback-test.xml new file mode 100644 index 00000000000..e5cbbb9c22e --- /dev/null +++ b/hapi-fhir-examples-uploader/src/main/resources/logback-test.xml @@ -0,0 +1,30 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java index ff59029af54..d10b16a7cfb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java @@ -50,6 +50,7 @@ import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.base.composite.BaseHumanNameDt; import ca.uhn.fhir.model.dstu2.composite.AddressDt; +import ca.uhn.fhir.model.dstu2.composite.BoundCodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.ContactPointDt; @@ -58,9 +59,11 @@ import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.composite.QuantityDt; +import ca.uhn.fhir.model.dstu2.resource.Conformance.RestSecurity; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.dstu2.resource.Patient.Communication; import ca.uhn.fhir.model.dstu2.resource.Questionnaire; +import ca.uhn.fhir.model.dstu2.valueset.RestfulSecurityServiceEnum; import ca.uhn.fhir.model.primitive.BaseDateTimeDt; import ca.uhn.fhir.model.primitive.IntegerDt; import ca.uhn.fhir.model.primitive.StringDt; @@ -469,28 +472,21 @@ class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implements ISea } else if (nextObject instanceof CodeableConceptDt) { CodeableConceptDt nextCC = (CodeableConceptDt) nextObject; if (!nextCC.getTextElement().isEmpty()) { - ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(nextSpDef.getName(), BaseFhirDao.normalizeString(nextCC.getTextElement().getValue()), nextCC.getTextElement().getValue()); + String value = nextCC.getTextElement().getValue(); + if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { + value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); + } + ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(nextSpDef.getName(), BaseFhirDao.normalizeString(value), value); nextEntity.setResource(theEntity); retVal.add(nextEntity); } - for (CodingDt nextCoding : nextCC.getCoding()) { - if (nextCoding.isEmpty()) { - continue; - } - - String nextSystem = nextCoding.getSystemElement().getValueAsString(); - String nextCode = nextCoding.getCodeElement().getValue(); - if (isNotBlank(nextSystem) || isNotBlank(nextCode)) { - systems.add(nextSystem); - codes.add(nextCode); - } - - if (!nextCoding.getDisplayElement().isEmpty()) { - systems.add(null); - codes.add(nextCoding.getDisplayElement().getValue()); - } - + extractTokensFromCodeableConcept(systems, codes, nextCC); + } else if (nextObject instanceof RestSecurity) { + // Conformance.security search param points to something kind of useless right now - This should probably be fixed. + RestSecurity sec = (RestSecurity)nextObject; + for (BoundCodeableConceptDt nextCC : sec.getService()) { + extractTokensFromCodeableConcept(systems, codes, nextCC); } } else { if (!multiType) { @@ -538,4 +534,25 @@ class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implements ISea return retVal; } + private void extractTokensFromCodeableConcept(List systems, List codes, CodeableConceptDt nextCC) { + for (CodingDt nextCoding : nextCC.getCoding()) { + if (nextCoding.isEmpty()) { + continue; + } + + String nextSystem = nextCoding.getSystemElement().getValueAsString(); + String nextCode = nextCoding.getCodeElement().getValue(); + if (isNotBlank(nextSystem) || isNotBlank(nextCode)) { + systems.add(nextSystem); + codes.add(nextCode); + } + + if (!nextCoding.getDisplayElement().isEmpty()) { + systems.add(null); + codes.add(nextCoding.getDisplayElement().getValue()); + } + + } + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java index df08b6da1d2..c001a48c0cd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.Set; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.hamcrest.core.StringContains; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -37,6 +38,8 @@ import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jmx.access.InvalidInvocationException; +import com.ctc.wstx.util.StringUtil; + import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.entity.TagTypeEnum; @@ -1715,6 +1718,37 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest { } + + @Test + public void testSearchStringParamReallyLong() { + String methodName = "testSearchStringParamReallyLong"; + String value = StringUtils.rightPad(methodName, 200, 'a'); + + IIdType longId; + IIdType shortId; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().addFamily(value); + longId = (IdDt) ourPatientDao.create(patient).getId().toUnqualifiedVersionless(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + shortId = (IdDt) ourPatientDao.create(patient).getId().toUnqualifiedVersionless(); + } + + Map params = new HashMap(); + String substring = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); + params.put(Patient.SP_FAMILY, new StringParam(substring)); + IBundleProvider found = ourPatientDao.search(params); + assertEquals(1, toList(found).size()); + assertThat(toUnqualifiedVersionlessIds(found), contains(longId)); + assertThat(toUnqualifiedVersionlessIds(found), not(contains(shortId))); + + } + + @Test public void testSearchStringParamWithNonNormalized() { { diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java b/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java index af8903896c1..2b692133a79 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java @@ -25,11 +25,12 @@ public class UhnFhirTestApp { int myPort = 8888; String base = "http://localhost:" + myPort + "/base"; - + // new File("target/testdb").mkdirs(); System.setProperty("fhir.db.location", "./target/testdb"); System.setProperty("fhir.db.location.dstu2", "./target/testdb_dstu2"); - System.setProperty("fhir.baseurl", base); + System.setProperty("fhir.baseurl.dstu1", base + "Dstu1"); + System.setProperty("fhir.baseurl.dstu2", base + "Dstu1"); Server server = new Server(myPort); diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/Perf.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/Perf.java new file mode 100644 index 00000000000..9b323d014f7 --- /dev/null +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/Perf.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.context; + +import java.io.IOException; + +import org.apache.commons.io.IOUtils; + +import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; +import ca.uhn.fhir.model.dstu.resource.DiagnosticReport; + +public class Perf { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Perf.class); + + public static void main(String[] args) throws Exception { + + FhirContext ctx = FhirContext.forDstu1(); + + String str = IOUtils.toString(Perf.class.getResourceAsStream("/contained-diagnosticreport-singlecontainedelement.xml")); + DiagnosticReport rept = ctx.newXmlParser().parseResource(DiagnosticReport.class, str); + + long start = System.currentTimeMillis(); + + for (int i = 0; i < 100000; i++) { + if (i % 10 == 0) { + ourLog.info("Rep: " + i); + } + ctx.newTerser().getAllPopulatedChildElementsOfType(rept, BaseResourceReferenceDt.class); + } + + long delay = System.currentTimeMillis() - start; + ourLog.info("Took: {} ms", delay); + } + +} diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/IdDtTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/IdDtTest.java index 46b982203ec..7984677a078 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/IdDtTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/IdDtTest.java @@ -23,6 +23,7 @@ public class IdDtTest { id = new IdDt("#123"); assertEquals("#123", id.getValue()); + assertEquals("#123", id.toUnqualifiedVersionless().getValue()); assertTrue(id.isLocal()); id = new IdDt("#Medication/499059CE-CDD4-48BC-9014-528A35D15CED/_history/1"); diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java index a43221fe551..ebd54e50c18 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java @@ -500,6 +500,28 @@ public class XmlParserTest { } + + @Test + public void testEncodeContainedWithSelfReference() { + IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); + + // Create an organization, note that the organization does not have an ID + Organization org = new Organization(); + org.getName().setValue("Contained Test Organization"); + org.setPartOf(new ResourceReferenceDt(org)); + + // Create a patient + Patient patient = new Patient(); + patient.getManagingOrganization().setResource(org); + + String encoded = xmlParser.encodeResourceToString(patient); + ourLog.info(encoded); + assertThat(encoded, containsString("")); + assertThat(encoded, containsString("")); + } + + + @Test public void testEncodeContained() { IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 210bae5cbe4..bfcaa54785d 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -146,6 +146,10 @@ e.g. "Location.partof.partof.organization". Thanks to Ismael Sarmento Jr for reporting! + + Prevent crash when encoding resources with contained resources + if the contained resources contained a circular reference to each other +