From ca0929df07b8d435a3b756f89998f68e2aae79fc Mon Sep 17 00:00:00 2001 From: James Date: Thu, 3 Jul 2014 18:02:14 -0400 Subject: [PATCH] Work on narrative generator --- .../ca/uhn/fhir/context/RuntimeChildAny.java | 6 +- .../java/ca/uhn/fhir/model/api/Bundle.java | 7 +- .../model/api/ResourceMetadataKeyEnum.java | 89 +++++--- .../fhir/model/dstu/composite/CodingDt.java | 6 +- .../model/dstu/composite/IdentifierDt.java | 4 +- .../BaseThymeleafNarrativeGenerator.java | 209 ++++++++++++++---- .../DefaultThymeleafNarrativeGenerator.java | 2 + .../fhir/narrative/INarrativeGenerator.java | 4 + .../java/ca/uhn/fhir/parser/JsonParser.java | 3 + .../java/ca/uhn/fhir/parser/ParserState.java | 9 +- .../fhir/rest/param/IdentifierListParam.java | 8 +- .../uhn/fhir/rest/param/SearchParameter.java | 2 + .../uhn/fhir/rest/server/RestfulServer.java | 13 +- .../uhn/fhir/narrative/narratives.properties | 5 +- .../narrative/title/DiagnosticReport.html | 9 + .../ca/uhn/fhir/narrative/title/Patient.html | 9 + .../fhir/model/primitive/CodingDtTest.java | 41 ++++ .../model/primitive/IdentifierDtTest.java | 10 + ...efaultThymeleafNarrativeGeneratorTest.java | 14 +- .../ca/uhn/fhir/rest/server/SearchTest.java | 9 +- .../java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java | 7 + .../ca/uhn/fhir/jpa/dao/FhirResourceDao.java | 3 + .../uhn/fhir/jpa/dao/SearchParameterMap.java | 11 + .../uhn/fhir/jpa/entity/BaseHasResource.java | 13 ++ .../ResourceIndexedSearchParamToken.java | 5 +- .../ca/uhn/fhir/jpa/entity/ResourceTable.java | 1 + .../uhn/fhir/jpa/dao/FhirResourceDaoTest.java | 48 ++++ .../WEB-INF/templates/tmpl-home-welcome.html | 69 +++--- .../WEB-INF/hapi-fhir-tester-config.xml | 1 + .../src/main/webapp/WEB-INF/templates/.swp | Bin 12288 -> 0 bytes .../resources/vm/jpa_resource_provider.vm | 2 +- 31 files changed, 500 insertions(+), 119 deletions(-) create mode 100644 hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/title/DiagnosticReport.html create mode 100644 hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/title/Patient.html create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/model/primitive/CodingDtTest.java delete mode 100644 hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/.swp diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildAny.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildAny.java index f3b2beea93a..7df3d093d7e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildAny.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildAny.java @@ -60,9 +60,11 @@ public class RuntimeChildAny extends RuntimeChildChoiceDefinition { if (o1res && o2res) { return theO1.getSimpleName().compareTo(theO2.getSimpleName()); } else if (o1res) { - return 1; - }else { return -1; + } else if (o1res == false && o2res == false) { + return 0; + }else { + return 1; } }}); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java index ddda2ee9ff5..8ad3c33a293 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java @@ -221,7 +221,12 @@ public class Bundle extends BaseBundle /* implements IElement */{ RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); if (theResource.getId() != null && StringUtils.isNotBlank(theResource.getId().getValue())) { - entry.getTitle().setValue(def.getName() + " " + theResource.getId().getValue()); + String title = ResourceMetadataKeyEnum.TITLE.get(theResource); + if (title != null) { + entry.getTitle().setValue(title); + } else { + entry.getTitle().setValue(def.getName() + " " + theResource.getId().getValue()); + } StringBuilder b = new StringBuilder(); b.append(theServerBase); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java index b445058651c..14f164fcf92 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java @@ -25,6 +25,8 @@ import static org.apache.commons.lang3.StringUtils.*; import java.util.Date; import java.util.Map; +import org.apache.commons.lang3.StringUtils; + import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -71,7 +73,7 @@ public abstract class ResourceMetadataKeyEnum { theResource.getResourceMetadata().put(PREVIOUS_ID, theObject); } }; - + /** * The value for this key is the bundle entry Published time. This is * defined by FHIR as "Time resource copied into the feed", which is @@ -128,6 +130,26 @@ public abstract class ResourceMetadataKeyEnum { theResource.getResourceMetadata().put(TAG_LIST, theObject); } }; + + /** + * If present and populated with a string (as an instance of {@link String}), + * this value contains the title for this resource, as supplied in any bundles containing the + * resource. + *

+ * Values for this key are of type {@link String} + *

+ */ + public static final ResourceMetadataKeyEnum TITLE = new ResourceMetadataKeyEnum("TITLE") { + @Override + public String get(IResource theResource) { + return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), TITLE); + } + + @Override + public void put(IResource theResource, String theObject) { + theResource.getResourceMetadata().put(TITLE, theObject); + } + }; /** @@ -182,7 +204,6 @@ public abstract class ResourceMetadataKeyEnum { } - @Override public boolean equals(Object obj) { if (this == obj) @@ -200,6 +221,8 @@ public abstract class ResourceMetadataKeyEnum { return true; } + + public abstract T get(IResource theResource); @Override @@ -210,6 +233,10 @@ public abstract class ResourceMetadataKeyEnum { return result; } + private String name() { + return myValue; + } + public abstract void put(IResource theResource, T theObject); @Override @@ -217,32 +244,28 @@ public abstract class ResourceMetadataKeyEnum { return myValue; } - private String name() { - return myValue; + private static IdDt getIdFromMetadataOrNullIfNone(Map, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { + Object retValObj = theResourceMetadata.get(theKey); + if (retValObj == null) { + return null; + } else if (retValObj instanceof String) { + if (isNotBlank((String) retValObj)) { + return new IdDt((String) retValObj); + } else { + return null; + } + } else if (retValObj instanceof IdDt) { + if (((IdDt) retValObj).isEmpty()) { + return null; + } else { + return (IdDt) retValObj; + } + } else if (retValObj instanceof Number) { + return new IdDt(((Number)retValObj).toString()); + } + throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName()); } - private static IdDt getIdFromMetadataOrNullIfNone(Map, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { - Object retValObj = theResourceMetadata.get(theKey); - if (retValObj == null) { - return null; - } else if (retValObj instanceof String) { - if (isNotBlank((String) retValObj)) { - return new IdDt((String) retValObj); - } else { - return null; - } - } else if (retValObj instanceof IdDt) { - if (((IdDt) retValObj).isEmpty()) { - return null; - } else { - return (IdDt) retValObj; - } - } else if (retValObj instanceof Number) { - return new IdDt(((Number)retValObj).toString()); - } - throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName()); - } - private static InstantDt getInstantFromMetadataOrNullIfNone(Map, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { Object retValObj = theResourceMetadata.get(theKey); if (retValObj == null) { @@ -259,4 +282,18 @@ public abstract class ResourceMetadataKeyEnum { throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + InstantDt.class.getCanonicalName()); } + private static String getStringFromMetadataOrNullIfNone(Map, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { + Object retValObj = theResourceMetadata.get(theKey); + if (retValObj == null) { + return null; + } else if (retValObj instanceof String) { + if (StringUtils.isBlank(((String) retValObj))) { + return null; + } else { + return (String) retValObj; + } + } + throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + String.class.getCanonicalName()); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/CodingDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/CodingDt.java index c4947031186..368f42e1aa2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/CodingDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/CodingDt.java @@ -407,9 +407,11 @@ public class CodingDt @Override public String getValueAsQueryToken() { if (org.apache.commons.lang3.StringUtils.isNotBlank(getSystem().getValueAsString())) { - return getSystem().getValueAsString() + '|' + getCode().getValueAsString(); - } else { + return getSystem().getValueAsString() + '|' + getCode().getValueAsString(); + } else if (getSystem().getValue()==null) { return getCode().getValueAsString(); + } else { + return '|' + getCode().getValueAsString(); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/IdentifierDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/IdentifierDt.java index e4f0e90eb1d..de4811ab332 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/IdentifierDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/IdentifierDt.java @@ -413,7 +413,9 @@ public class IdentifierDt @Override public String getValueAsQueryToken() { if (org.apache.commons.lang3.StringUtils.isNotBlank(getSystem().getValueAsString())) { - return getSystem().getValueAsString() + '|' + getValue().getValueAsString(); + return getSystem().getValueAsString() + '|' + getValue().getValueAsString(); + } else if (getSystem().getValue() == null) { + return getValue().getValueAsString(); } else { return '|' + getValue().getValueAsString(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java index 2b3de6fdac6..d2622224797 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java @@ -69,17 +69,100 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener private boolean myIgnoreMissingTemplates = true; private TemplateEngine myProfileTemplateEngine; - private HashMap myProfileToNarrativeName; - private HashMap, String> myClassToNarrativeName; + private TemplateEngine myTitleTemplateEngine; + private HashMap myProfileToName; + private HashMap, String> myClassToName; private HashMap myNameToNarrativeTemplate; - private boolean myApplyDefaultDatatypeTemplates=true; + private boolean myApplyDefaultDatatypeTemplates = true; private volatile boolean myInitialized; + private HashMap myNameToTitleTemplate; + @Override public NarrativeDt generateNarrative(IResource theResource) { return generateNarrative(null, theResource); } + @Override + public String generateTitle(IResource theResource) { + return generateTitle(null, theResource); + } + + @Override + public String generateTitle(String theProfile, IResource theResource) { + if (!myInitialized) { + initialize(); + } + + String name = null; + if (StringUtils.isNotBlank(theProfile)) { + name = myProfileToName.get(theProfile); + } + if (name == null) { + name = myClassToName.get(theResource.getClass()); + } + + if (name == null) { + if (myIgnoreMissingTemplates) { + ourLog.debug("No title template available for profile: {}", theProfile); + return null; + } else { + throw new DataFormatException("No title template for class " + theResource.getClass().getCanonicalName()); + } + } + + try { + Context context = new Context(); + context.setVariable("resource", theResource); + + String result = myTitleTemplateEngine.process(name, context); + StringBuilder b = new StringBuilder(); + boolean inTag = false; + for (int i = 0; i < result.length(); i++) { + char nextChar = result.charAt(i); + char prevChar = i > 0 ? result.charAt(i - 1) : '\n'; + if (nextChar == '<') { + inTag = true; + continue; + } else if (inTag) { + if (nextChar == '>') { + inTag = false; + } + continue; + } else if (nextChar <= ' ') { + if (prevChar <= ' ' || prevChar == '>') { + continue; + } else { + b.append(' '); + } + } else { + b.append(nextChar); + } + } + + while (b.length() > 0 && b.charAt(b.length()-1) == ' ') { + b.setLength(b.length() - 1); + } + + result = b.toString(); + if (result.startsWith("<") && result.contains(">")) { + result = result.substring(result.indexOf('>') + 1); + } + if (result.endsWith(">") && result.contains("<")) { + result = result.substring(0, result.lastIndexOf('<')); + } + + return result; + } catch (Exception e) { + if (myIgnoreFailures) { + ourLog.error("Failed to generate narrative", e); + return "No title available - Error: " + e.getMessage(); + } else { + throw new DataFormatException(e); + } + } + } + @Override public NarrativeDt generateNarrative(String theProfile, IResource theResource) { if (!myInitialized) { @@ -88,10 +171,10 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener String name = null; if (StringUtils.isNotBlank(theProfile)) { - name = myProfileToNarrativeName.get(theProfile); + name = myProfileToName.get(theProfile); } if (name == null) { - name = myClassToNarrativeName.get(theResource.getClass()); + name = myClassToName.get(theResource.getClass()); } if (name == null) { @@ -106,7 +189,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener try { Context context = new Context(); context.setVariable("resource", theResource); - + String result = myProfileTemplateEngine.process(name, context); if (myCleanWhitespace) { @@ -131,9 +214,10 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener if (myInitialized) { return; } - myProfileToNarrativeName = new HashMap(); - myClassToNarrativeName = new HashMap, String>(); + myProfileToName = new HashMap(); + myClassToName = new HashMap, String>(); myNameToNarrativeTemplate = new HashMap(); + myNameToTitleTemplate = new HashMap(); List propFileName = getPropertyFile(); @@ -160,6 +244,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener myProfileTemplateEngine.setDialect(dialect); myProfileTemplateEngine.initialize(); } + { + myTitleTemplateEngine = new TemplateEngine(); + TemplateResolver resolver = new TemplateResolver(); + resolver.setResourceResolver(new TitleResourceResolver()); + myTitleTemplateEngine.setTemplateResolver(resolver); + StandardDialect dialect = new StandardDialect(); + myTitleTemplateEngine.setDialect(dialect); + myTitleTemplateEngine.initialize(); + } myInitialized = true; } @@ -167,12 +260,9 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener protected abstract List getPropertyFile(); /** - * If set to true (which is the default), most whitespace will - * be trimmed from the generated narrative before it is returned. + * If set to true (which is the default), most whitespace will be trimmed from the generated narrative before it is returned. *

- * Note that in order to preserve formatting, not all whitespace is trimmed. - * Repeated whitespace characters (e.g. "\n \n ") will be - * trimmed to a single space. + * Note that in order to preserve formatting, not all whitespace is trimmed. Repeated whitespace characters (e.g. "\n \n ") will be trimmed to a single space. *

*/ public boolean isCleanWhitespace() { @@ -180,30 +270,24 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener } /** - * If set to true, which is the default, if any failure occurs - * during narrative generation the generator will suppress any generated - * exceptions, and simply return a default narrative indicating that no - * narrative is available. + * If set to true, which is the default, if any failure occurs during narrative generation the generator will suppress any generated exceptions, and simply return a default narrative + * indicating that no narrative is available. */ public boolean isIgnoreFailures() { return myIgnoreFailures; } /** - * If set to true, will return an empty narrative block for any profiles - * where no template is available + * If set to true, will return an empty narrative block for any profiles where no template is available */ public boolean isIgnoreMissingTemplates() { return myIgnoreMissingTemplates; } /** - * If set to true (which is the default), most whitespace will - * be trimmed from the generated narrative before it is returned. + * If set to true (which is the default), most whitespace will be trimmed from the generated narrative before it is returned. *

- * Note that in order to preserve formatting, not all whitespace is trimmed. - * Repeated whitespace characters (e.g. "\n \n ") will be - * trimmed to a single space. + * Note that in order to preserve formatting, not all whitespace is trimmed. Repeated whitespace characters (e.g. "\n \n ") will be trimmed to a single space. *

*/ public void setCleanWhitespace(boolean theCleanWhitespace) { @@ -211,18 +295,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener } /** - * If set to true, which is the default, if any failure occurs - * during narrative generation the generator will suppress any generated - * exceptions, and simply return a default narrative indicating that no - * narrative is available. + * If set to true, which is the default, if any failure occurs during narrative generation the generator will suppress any generated exceptions, and simply return a default narrative + * indicating that no narrative is available. */ public void setIgnoreFailures(boolean theIgnoreFailures) { myIgnoreFailures = theIgnoreFailures; } /** - * If set to true, will return an empty narrative block for any profiles - * where no template is available + * If set to true, will return an empty narrative block for any profiles where no template is available */ public void setIgnoreMissingTemplates(boolean theIgnoreMissingTemplates) { myIgnoreMissingTemplates = theIgnoreMissingTemplates; @@ -230,7 +311,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener private void loadProperties(String propFileName) throws IOException { ourLog.debug("Loading narrative properties file: {}", propFileName); - + Properties file = new Properties(); InputStream resource = loadResource(propFileName); @@ -245,13 +326,22 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener String narrativePropName = name + ".narrative"; String narrativeName = file.getProperty(narrativePropName); - if (isBlank(narrativeName)) { - throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' in file " + propFileName); + String titlePropName = name + ".title"; + String titleName = file.getProperty(titlePropName); + if (isBlank(narrativeName) && isBlank(titleName)) { + throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' or '" + titlePropName + "' in file " + propFileName); } - String narrative = IOUtils.toString(loadResource(narrativeName)); - myProfileToNarrativeName.put(file.getProperty(nextKey), name); - myNameToNarrativeTemplate.put(name, narrative); + myProfileToName.put(file.getProperty(nextKey), name); + + if (StringUtils.isNotBlank(narrativeName)) { + String narrative = IOUtils.toString(loadResource(narrativeName)); + myNameToNarrativeTemplate.put(name, narrative); + } + if (StringUtils.isNotBlank(titleName)) { + String title = IOUtils.toString(loadResource(titleName)); + myNameToTitleTemplate.put(name, title); + } } else if (nextKey.endsWith(".class")) { @@ -262,9 +352,9 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener String className = file.getProperty(nextKey); - Class dtClass; + Class clazz; try { - dtClass = Class.forName(className); + clazz = Class.forName(className); } catch (ClassNotFoundException e) { ourLog.warn("Unknown datatype class '{}' identified in narrative file {}", name, propFileName); continue; @@ -272,16 +362,27 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener String narrativePropName = name + ".narrative"; String narrativeName = file.getProperty(narrativePropName); - if (isBlank(narrativeName)) { - throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' in file " + propFileName); + String titlePropName = name + ".title"; + String titleName = file.getProperty(titlePropName); + if (isBlank(narrativeName) && isBlank(titleName)) { + throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' or '" + titlePropName + "' in file " + propFileName); } - String narrative = IOUtils.toString(loadResource(narrativeName)); - myClassToNarrativeName.put(dtClass, name); - myNameToNarrativeTemplate.put(name, narrative); + myClassToName.put(clazz, name); + + if (StringUtils.isNotBlank(narrativeName)) { + String narrative = IOUtils.toString(loadResource(narrativeName)); + myNameToNarrativeTemplate.put(name, narrative); + } + if (StringUtils.isNotBlank(titleName)) { + String title = IOUtils.toString(loadResource(titleName)); + myNameToTitleTemplate.put(name, title); + } } else if (nextKey.endsWith(".narrative")) { continue; + } else if (nextKey.endsWith(".title")) { + continue; } else { throw new ConfigurationException("Invalid property name: " + nextKey); } @@ -379,14 +480,13 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue); final Object value = expression.execute(configuration, theArguments); - theElement.removeAttribute(theAttributeName); theElement.clearChildren(); Context context = new Context(); context.setVariable("resource", value); - - String name = myClassToNarrativeName.get(value.getClass()); + + String name = myClassToName.get(value.getClass()); if (name == null) { if (myIgnoreMissingTemplates) { ourLog.debug("No narrative template available for type: {}", value.getClass()); @@ -438,4 +538,21 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener return new ReaderInputStream(new StringReader(template)); } } + + private final class TitleResourceResolver implements IResourceResolver { + @Override + public String getName() { + return getClass().getCanonicalName(); + } + + @Override + public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theName) { + String template = myNameToTitleTemplate.get(theName); + if (template == null) { + ourLog.info("No narative template for resource profile: {}", theName); + return new ReaderInputStream(new StringReader("")); + } + return new ReaderInputStream(new StringReader(template)); + } + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGenerator.java index 28ce4355fac..75faf958d1f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGenerator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGenerator.java @@ -64,4 +64,6 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe return myUseHapiServerConformanceNarrative; } + + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/INarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/INarrativeGenerator.java index 80060c1042d..8ac9ec3d64a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/INarrativeGenerator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/INarrativeGenerator.java @@ -30,4 +30,8 @@ public interface INarrativeGenerator { NarrativeDt generateNarrative(IResource theResource); + String generateTitle(IResource theResource); + + String generateTitle(String theProfile, IResource theResource); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 8e98094a7a6..3b4b8c8bf8d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -222,6 +222,7 @@ public class JsonParser extends BaseParser implements IParser { eventWriter.writeEnd(); // entry array eventWriter.writeEnd(); + eventWriter.flush(); eventWriter.close(); } @@ -506,6 +507,7 @@ public class JsonParser extends BaseParser implements IParser { RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); encodeResourceToJsonStreamWriter(resDef, theResource, eventWriter, null,false); eventWriter.flush(); + eventWriter.close(); } @Override @@ -536,6 +538,7 @@ public class JsonParser extends BaseParser implements IParser { eventWriter.writeEnd(); eventWriter.flush(); + eventWriter.close(); } /** 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 6d25614122d..2fd3c1824c6 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 @@ -417,6 +417,9 @@ class ParserState { if (myEntry.getUpdated().isEmpty() == false) { ResourceMetadataKeyEnum.UPDATED.put(myEntry.getResource(), myEntry.getUpdated()); } + + ResourceMetadataKeyEnum.TITLE.put(myEntry.getResource(), myEntry.getTitle().getValue()); + if (myEntry.getCategories().isEmpty() == false) { TagList tagList = new TagList(); for (Tag next : myEntry.getCategories()) { @@ -608,7 +611,6 @@ class ParserState { myPreResourceState = thePreResourceState; } - @SuppressWarnings("unused") public void attributeValue(String theName, String theValue) throws DataFormatException { // ignore by default } @@ -617,7 +619,6 @@ class ParserState { // ignore by default } - @SuppressWarnings("unused") public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { // ignore by default } @@ -657,7 +658,7 @@ class ParserState { myStack = theState; } - public void string(@SuppressWarnings("unused") String theData) { + public void string(String theData) { // ignore by default } @@ -665,7 +666,7 @@ class ParserState { // allow an implementor to override } - public void xmlEvent(@SuppressWarnings("unused") XMLEvent theNextEvent) { + public void xmlEvent(XMLEvent theNextEvent) { // ignore } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IdentifierListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IdentifierListParam.java index 46103f4d90e..e0d8c047481 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IdentifierListParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IdentifierListParam.java @@ -31,7 +31,7 @@ import ca.uhn.fhir.rest.method.QualifiedParamList; public class IdentifierListParam implements IQueryParameterOr { private List myIdentifiers = new ArrayList(); - + /** * Returns all identifiers associated with this list */ @@ -64,4 +64,10 @@ public class IdentifierListParam implements IQueryParameterOr { } } + public void addIdentifier(IdentifierDt theIdentifierDt) { + if (theIdentifierDt != null && theIdentifierDt.isEmpty() == false) { + getIdentifiers().add(theIdentifierDt); + } + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java index 2546a9af512..ca12a833c98 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java @@ -160,6 +160,8 @@ public class SearchParameter extends BaseQueryParameter { myParamType = SearchParamTypeEnum.QUANTITY; } else if (ReferenceParam.class.isAssignableFrom(type)) { myParamType = SearchParamTypeEnum.REFERENCE; + } else if (IdentifierListParam.class.isAssignableFrom(type)) { + myParamType = SearchParamTypeEnum.TOKEN; } else { throw new ConfigurationException("Unknown search parameter type: " + type); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index fdac21d4935..b97a602832e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -45,6 +45,7 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -557,7 +558,7 @@ public class RestfulServer extends HttpServlet { statusCode=((BaseServerResponseException) e).getStatusCode(); issue.getDetails().setValue(e.getMessage()); } else { - ourLog.warn("Failure during REST processing: {}", e.toString()); + ourLog.error("Failure during REST processing: {}"+ e.toString(), e); issue.getDetails().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e)); } @@ -779,6 +780,14 @@ public class RestfulServer extends HttpServlet { bundle.getLinkSelf().setValue(theCompleteUrl); for (IResource next : theResult) { + + if (theContext.getNarrativeGenerator() != null) { + String title = theContext.getNarrativeGenerator().generateTitle(next); + if (StringUtils.isNotBlank(title)) { + ResourceMetadataKeyEnum.TITLE.put(next, title); + } + } + bundle.addResource(next, theContext, theServerBase); } @@ -1019,6 +1028,7 @@ public class RestfulServer extends HttpServlet { RestfulServer.getNewParser(theServer.getFhirContext(), theResponseEncoding, thePrettyPrint, theNarrativeMode).encodeBundleToWriter(bundle, writer); } } finally { + writer.flush(); writer.close(); } } @@ -1093,6 +1103,7 @@ public class RestfulServer extends HttpServlet { RestfulServer.getNewParser(theServer.getFhirContext(), theResponseEncoding, thePrettyPrint, theNarrativeMode).encodeResourceToWriter(theResource, writer); } } finally { + writer.flush(); writer.close(); } } diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narratives.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narratives.properties index c28153ec1ea..0062367beb8 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narratives.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narratives.properties @@ -32,6 +32,9 @@ quantity.narrative=classpath:ca/uhn/fhir/narrative/QuantityDt.html patient.class=ca.uhn.fhir.model.dstu.resource.Patient patient.narrative=classpath:ca/uhn/fhir/narrative/Patient.html +patient.title=classpath:ca/uhn/fhir/narrative/title/Patient.html + diagnosticreport.class=ca.uhn.fhir.model.dstu.resource.DiagnosticReport -diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html \ No newline at end of file +diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html +diagnosticreport.title=classpath:ca/uhn/fhir/narrative/title/DiagnosticReport.html diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/title/DiagnosticReport.html b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/title/DiagnosticReport.html new file mode 100644 index 00000000000..733c936c5a7 --- /dev/null +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/title/DiagnosticReport.html @@ -0,0 +1,9 @@ +
+ + + + + + + +
\ No newline at end of file diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/title/Patient.html b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/title/Patient.html new file mode 100644 index 00000000000..6b94c7e9760 --- /dev/null +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/title/Patient.html @@ -0,0 +1,9 @@ +
+ Dr + John + SMITH + Jr + + (8708660) + +
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/primitive/CodingDtTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/primitive/CodingDtTest.java new file mode 100644 index 00000000000..9f637a6b4ea --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/primitive/CodingDtTest.java @@ -0,0 +1,41 @@ +package ca.uhn.fhir.model.primitive; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ca.uhn.fhir.model.dstu.composite.CodingDt; + +public class CodingDtTest { + + @Test + public void testTokenWithPipeInValue() { + CodingDt dt = new CodingDt(); + dt.setValueAsQueryToken(null, "a|b|c"); + + assertEquals("a", dt.getSystem().getValueAsString()); + assertEquals("b|c", dt.getCode().getValue()); + assertEquals("a|b|c", dt.getValueAsQueryToken()); + } + + @Test + public void testTokenWithPipeInValueAndNoSystem() { + CodingDt dt = new CodingDt(); + dt.setValueAsQueryToken(null, "|b|c"); + + assertEquals("", dt.getSystem().getValueAsString()); + assertEquals("b|c", dt.getCode().getValue()); + assertEquals("|b|c", dt.getValueAsQueryToken()); + } + + @Test + public void testTokenNoSystem() { + CodingDt dt = new CodingDt(); + dt.setValueAsQueryToken(null, "c"); + + assertEquals(null, dt.getSystem().getValueAsString()); + assertEquals("c", dt.getCode().getValue()); + assertEquals("c", dt.getValueAsQueryToken()); + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/primitive/IdentifierDtTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/primitive/IdentifierDtTest.java index b2465f5f1e5..32347442f1f 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/primitive/IdentifierDtTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/primitive/IdentifierDtTest.java @@ -28,4 +28,14 @@ public class IdentifierDtTest { assertEquals("|b|c", dt.getValueAsQueryToken()); } + @Test + public void testTokenNoSystem() { + IdentifierDt dt = new IdentifierDt(); + dt.setValueAsQueryToken(null, "c"); + + assertEquals(null, dt.getSystem().getValueAsString()); + assertEquals("c", dt.getValue().getValue()); + assertEquals("c", dt.getValueAsQueryToken()); + } + } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java index 0e03ceea7d8..e2e0ef0bb6a 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java @@ -46,9 +46,12 @@ public class DefaultThymeleafNarrativeGeneratorTest { value.setBirthDate(new Date(), TemporalPrecisionEnum.DAY); - String output = gen.generateNarrative("http://hl7.org/fhir/profiles/Patient", value).getDiv().getValueAsString(); - - ourLog.info(output); + String output = gen.generateNarrative(value).getDiv().getValueAsString(); + assertThat(output, StringContains.containsString("
joe john BLOW
")); + + String title = gen.generateTitle(value); + assertEquals("joe john BLOW (123456)", title); + ourLog.info(title); } @Test @@ -105,6 +108,11 @@ public class DefaultThymeleafNarrativeGeneratorTest { ourLog.info(output); assertThat(output, StringContains.containsString("
Some Diagnostic Report
")); + String title = gen.generateTitle(value); + ourLog.info(title); + assertEquals("Some Diagnostic Report - final - 2 observations", title); + + // Now try it with the parser FhirContext context = new FhirContext(); diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java index 03fa904a2b2..56fea2d6202 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java @@ -23,9 +23,8 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.testutil.RandomServerPortProvider; @@ -36,7 +35,6 @@ import ca.uhn.fhir.testutil.RandomServerPortProvider; public class SearchTest { private static CloseableHttpClient ourClient; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchTest.class); private static int ourPort; private static Server ourServer; private static FhirContext ourCtx = new FhirContext(); @@ -46,12 +44,14 @@ public class SearchTest { HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa"); HttpResponse status = ourClient.execute(httpGet); String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); assertEquals(200, status.getStatusLine().getStatusCode()); Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); assertEquals(1, bundle.getEntries().size()); Patient p = bundle.getResources(Patient.class).get(0); assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString()); + assertEquals("IDAAA (identifier123)", bundle.getEntries().get(0).getTitle().getValue()); } @AfterClass @@ -68,6 +68,8 @@ public class SearchTest { ServletHandler proxyHandler = new ServletHandler(); RestfulServer servlet = new RestfulServer(); + servlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); + servlet.setResourceProviders(patientProvider); ServletHolder servletHolder = new ServletHolder(servlet); proxyHandler.addServletWithMapping(servletHolder, "/*"); @@ -92,6 +94,7 @@ public class SearchTest { Patient patient = new Patient(); patient.setId("1"); + patient.addIdentifier("system", "identifier123"); patient.addName().addFamily("id"+theParam.getValue()); retVal.add(patient); return retVal; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java index de1e75b5462..da2af0ad0f0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java @@ -747,6 +747,9 @@ public abstract class BaseFhirDao { } } + String title = ResourceMetadataKeyEnum.TITLE.get(theResource); + theEntity.setTitle(title); + } protected ResourceTable toEntity(IResource theResource) { @@ -784,6 +787,10 @@ public abstract class BaseFhirDao { retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, theEntity.getPublished()); retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, theEntity.getUpdated()); + if (theEntity.getTitle()!=null) { + ResourceMetadataKeyEnum.TITLE.put(retVal, theEntity.getTitle()); + } + if (theEntity.getDeleted()!=null) { ResourceMetadataKeyEnum.DELETED_AT.put(retVal, new InstantDt(theEntity.getDeleted())); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java index 341f92c2547..9ffa64e8bf4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java @@ -431,7 +431,10 @@ public class FhirResourceDao extends BaseFhirDao implements ArrayList singleCodePredicates = (new ArrayList()); if (StringUtils.isNotBlank(system)) { singleCodePredicates.add(builder.equal(from.get("mySystem"), system)); + } else if (system == null) { + // don't check the system } else { + // If the system is "", we only match on null systems singleCodePredicates.add(builder.isNull(from.get("mySystem"))); } if (StringUtils.isNotBlank(code)) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java index e8b8c090b1d..97d58dcdcb4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java @@ -35,6 +35,17 @@ public class SearchParameterMap extends HashMap>()); + } + + get(theName).add(theOr.getValuesAsQueryTokens()); + } + public void add(String theName, IQueryParameterType theParam) { if (theParam == null) { return; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java index af13ee05c8d..314200ea5c6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java @@ -17,6 +17,8 @@ import ca.uhn.fhir.model.primitive.InstantDt; @MappedSuperclass public abstract class BaseHasResource { + private static final int MAX_TITLE_LENGTH = 100; + @Column(name = "RES_DELETED_AT", nullable = true) @Temporal(TemporalType.TIMESTAMP) private Date myDeleted; @@ -33,6 +35,9 @@ public abstract class BaseHasResource { @Lob() private byte[] myResource; + @Column(name = "RES_TITLE", nullable = true, length = MAX_TITLE_LENGTH) + private String myTitle; + @Temporal(TemporalType.TIMESTAMP) @Column(name = "RES_UPDATED", nullable = false) private Date myUpdated; @@ -61,6 +66,10 @@ public abstract class BaseHasResource { public abstract Collection getTags(); + public String getTitle() { + return myTitle; + } + public InstantDt getUpdated() { return new InstantDt(myUpdated); } @@ -87,6 +96,10 @@ public abstract class BaseHasResource { myResource = theResource; } + public void setTitle(String theTitle) { + myTitle = theTitle; + } + public void setUpdated(Date theUpdated) { myUpdated = theUpdated; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java index 1b0d40c58d8..3f11b2e1d8f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java @@ -8,7 +8,10 @@ import org.apache.commons.lang3.StringUtils; @Entity @Table(name = "HFJ_SPIDX_TOKEN" /* , indexes = { @Index(name = "IDX_SP_TOKEN", columnList = "SP_SYSTEM,SP_VALUE") } */) -@org.hibernate.annotations.Table(appliesTo = "HFJ_SPIDX_TOKEN", indexes = { @org.hibernate.annotations.Index(name = "IDX_SP_TOKEN", columnNames = { "RES_TYPE", "SP_NAME", "SP_SYSTEM", "SP_VALUE" }) }) +@org.hibernate.annotations.Table(appliesTo = "HFJ_SPIDX_TOKEN", indexes = { + @org.hibernate.annotations.Index(name = "IDX_SP_TOKEN", columnNames = { "RES_TYPE", "SP_NAME", "SP_SYSTEM", "SP_VALUE" }), + @org.hibernate.annotations.Index(name = "IDX_SP_TOKEN_UNQUAL", columnNames = { "RES_TYPE", "SP_NAME", "SP_VALUE" }) +}) public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam { private static final long serialVersionUID = 1L; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java index a89a945b133..2787e1ba957 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java @@ -252,6 +252,7 @@ public class ResourceTable extends BaseHasResource implements Serializable { retVal.setResourceType(myResourceType); retVal.setVersion(myVersion); + retVal.setTitle(getTitle()); retVal.setPublished(getPublished()); retVal.setUpdated(getUpdated()); retVal.setEncoding(getEncoding()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java index b9ae72c9eaa..3f0e193d86c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java @@ -39,7 +39,9 @@ import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; +import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.IdentifierListParam; import ca.uhn.fhir.rest.param.QualifiedDateParam; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; @@ -122,6 +124,50 @@ public class FhirResourceDaoTest { assertEquals(o1id.toUnqualifiedVersionless(), p1.getManagingOrganization().getReference().toUnqualifiedVersionless()); } + @Test + public void testSearchTokenParam() { + Patient patient = new Patient(); + patient.addIdentifier("urn:system", "testSearchTokenParam001"); + patient.addName().addFamily("Tester").addGiven("testSearchTokenParam1"); + ourPatientDao.create(patient); + + patient = new Patient(); + patient.addIdentifier("urn:system", "testSearchTokenParam002"); + patient.addName().addFamily("Tester").addGiven("testSearchTokenParam2"); + ourPatientDao.create(patient); + + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testSearchTokenParam001")); + IBundleProvider retrieved = ourPatientDao.search(map); + assertEquals(1, retrieved.size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new IdentifierDt(null, "testSearchTokenParam001")); + IBundleProvider retrieved = ourPatientDao.search(map); + assertEquals(1, retrieved.size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + IdentifierListParam listParam = new IdentifierListParam(); + listParam.addIdentifier(new IdentifierDt("urn:system", "testSearchTokenParam001")); + listParam.addIdentifier(new IdentifierDt("urn:system", "testSearchTokenParam002")); + map.add(Patient.SP_IDENTIFIER, listParam); + IBundleProvider retrieved = ourPatientDao.search(map); + assertEquals(2, retrieved.size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + IdentifierListParam listParam = new IdentifierListParam(); + listParam.addIdentifier(new IdentifierDt(null, "testSearchTokenParam001")); + listParam.addIdentifier(new IdentifierDt("urn:system", "testSearchTokenParam002")); + map.add(Patient.SP_IDENTIFIER, listParam); + IBundleProvider retrieved = ourPatientDao.search(map); + assertEquals(2, retrieved.size()); + } + } + @Test public void testIdParam() { Patient patient = new Patient(); @@ -360,6 +406,7 @@ public class FhirResourceDaoTest { Patient patient = new Patient(); patient.addIdentifier("urn:system", "001"); patient.addName().addFamily("testSearchNameParam01Fam").addGiven("testSearchNameParam01Giv"); + ResourceMetadataKeyEnum.TITLE.put(patient, "P1TITLE"); id1 = ourPatientDao.create(patient).getId(); } { @@ -374,6 +421,7 @@ public class FhirResourceDaoTest { List patients = toList(ourPatientDao.search(params)); assertEquals(1, patients.size()); assertEquals(id1.getIdPart(), patients.get(0).getId().getIdPart()); + assertEquals("P1TITLE", ResourceMetadataKeyEnum.TITLE.get(patients.get(0))); // Given name shouldn't return for family param params = new HashMap(); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html index 90ca83c831e..39c8f6b9b9e 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html @@ -1,35 +1,52 @@
+ +

+ This is the home for the FHIR test server operated by + University Health Network. This server + (and the testing application you are currently using to access it) + is entirely built using + HAPI-FHIR, + a 100% open-source Java implementation of the + FHIR specification. +

+

+ Here are some things you might wish to try: +

+
    +
  • + View a + list of patients + on this server. +
  • +
  • + Construct a + search query + on this server. +
  • +
  • + Access a + different server + (use the Server menu at the top of the page to see a list of public FHIR servers) +
  • +
+
+ +

+ You are accessing the public FHIR server + . This server is hosted elsewhere on the internet + but is being accessed using +

+

- This is the home for the FHIR test server operated by - University Health Network. -

-

- This is not a production server! + + list of patients - on this server. - -

  • - Construct a - search query - on this server. -
  • -
  • - Access a - different server - (use the Server menu at the top of the page to see a list of public FHIR servers) -
  • -
    diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml index 7475d779ff2..a7df352d042 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml @@ -11,6 +11,7 @@ + test, TEST, http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/ home , Localhost Server , http://localhost:8887/fhir/context hi , Health Intersections , http://fhir.healthintersections.com.au/open furore , Spark - Furore Reference Server , http://spark.furore.com/fhir diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/.swp b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/.swp deleted file mode 100644 index 14e1f4548ddaf8b6c430b5a2be58f5d111a2b02e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&y-UO}7{~FPo0Fpdz^Tx!FLxD`gDAK;2rlO;X&Y>%ZzS=+$=Uzfwt5`|Z#wAU zH_(JWEzd7}w$dcg)BVHzDu_ilj{E6*eBKE0sw!=_g&k|D5~H%l_{;b*D4SH3J6`4y z0tiebaJY__i>MF3I6s@8o~$R@2eAksfB*srAbL$i_k>#|FOD%A~np7FFS-*Y#g`n_>PPA-(`OM@c>a diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm index edae6f7ce18..b67613298e9 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm @@ -34,7 +34,7 @@ public class ${className}ResourceProvider extends JpaResourceProvider<${classNam #if (${param.type} == 'string' ) StringParam the${param.nameCapitalized}, #elseif (${param.type} == 'token' ) - IdentifierDt the${param.nameCapitalized}, + IdentifierListParam the${param.nameCapitalized}, #elseif (${param.type} == 'date' ) DateRangeParam the${param.nameCapitalized}, #elseif (${param.type} == 'quantity' )