Work on narrative generator

This commit is contained in:
James 2014-07-03 18:02:14 -04:00
parent c811c164b5
commit ca0929df07
31 changed files with 500 additions and 119 deletions

View File

@ -60,9 +60,11 @@ public class RuntimeChildAny extends RuntimeChildChoiceDefinition {
if (o1res && o2res) { if (o1res && o2res) {
return theO1.getSimpleName().compareTo(theO2.getSimpleName()); return theO1.getSimpleName().compareTo(theO2.getSimpleName());
} else if (o1res) { } else if (o1res) {
return 1;
}else {
return -1; return -1;
} else if (o1res == false && o2res == false) {
return 0;
}else {
return 1;
} }
}}); }});

View File

@ -221,7 +221,12 @@ public class Bundle extends BaseBundle /* implements IElement */{
RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
if (theResource.getId() != null && StringUtils.isNotBlank(theResource.getId().getValue())) { 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(); StringBuilder b = new StringBuilder();
b.append(theServerBase); b.append(theServerBase);

View File

@ -25,6 +25,8 @@ import static org.apache.commons.lang3.StringUtils.*;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -71,7 +73,7 @@ public abstract class ResourceMetadataKeyEnum<T> {
theResource.getResourceMetadata().put(PREVIOUS_ID, theObject); theResource.getResourceMetadata().put(PREVIOUS_ID, theObject);
} }
}; };
/** /**
* The value for this key is the bundle entry <b>Published</b> time. This is * The value for this key is the bundle entry <b>Published</b> time. This is
* defined by FHIR as "Time resource copied into the feed", which is * defined by FHIR as "Time resource copied into the feed", which is
@ -128,6 +130,26 @@ public abstract class ResourceMetadataKeyEnum<T> {
theResource.getResourceMetadata().put(TAG_LIST, theObject); 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.
* <p>
* Values for this key are of type <b>{@link String}</b>
* </p>
*/
public static final ResourceMetadataKeyEnum<String> TITLE = new ResourceMetadataKeyEnum<String>("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<T> {
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) if (this == obj)
@ -200,6 +221,8 @@ public abstract class ResourceMetadataKeyEnum<T> {
return true; return true;
} }
public abstract T get(IResource theResource); public abstract T get(IResource theResource);
@Override @Override
@ -210,6 +233,10 @@ public abstract class ResourceMetadataKeyEnum<T> {
return result; return result;
} }
private String name() {
return myValue;
}
public abstract void put(IResource theResource, T theObject); public abstract void put(IResource theResource, T theObject);
@Override @Override
@ -217,32 +244,28 @@ public abstract class ResourceMetadataKeyEnum<T> {
return myValue; return myValue;
} }
private String name() { private static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<?> theKey) {
return myValue; 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<ResourceMetadataKeyEnum<?>, 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<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<InstantDt> theKey) { private static InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<InstantDt> theKey) {
Object retValObj = theResourceMetadata.get(theKey); Object retValObj = theResourceMetadata.get(theKey);
if (retValObj == null) { if (retValObj == null) {
@ -259,4 +282,18 @@ public abstract class ResourceMetadataKeyEnum<T> {
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + InstantDt.class.getCanonicalName()); 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<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<String> 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());
}
} }

View File

@ -407,9 +407,11 @@ public class CodingDt
@Override @Override
public String getValueAsQueryToken() { public String getValueAsQueryToken() {
if (org.apache.commons.lang3.StringUtils.isNotBlank(getSystem().getValueAsString())) { if (org.apache.commons.lang3.StringUtils.isNotBlank(getSystem().getValueAsString())) {
return getSystem().getValueAsString() + '|' + getCode().getValueAsString(); return getSystem().getValueAsString() + '|' + getCode().getValueAsString();
} else { } else if (getSystem().getValue()==null) {
return getCode().getValueAsString(); return getCode().getValueAsString();
} else {
return '|' + getCode().getValueAsString();
} }
} }

View File

@ -413,7 +413,9 @@ public class IdentifierDt
@Override @Override
public String getValueAsQueryToken() { public String getValueAsQueryToken() {
if (org.apache.commons.lang3.StringUtils.isNotBlank(getSystem().getValueAsString())) { 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 { } else {
return '|' + getValue().getValueAsString(); return '|' + getValue().getValueAsString();
} }

View File

@ -69,17 +69,100 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
private boolean myIgnoreMissingTemplates = true; private boolean myIgnoreMissingTemplates = true;
private TemplateEngine myProfileTemplateEngine; private TemplateEngine myProfileTemplateEngine;
private HashMap<String, String> myProfileToNarrativeName; private TemplateEngine myTitleTemplateEngine;
private HashMap<Class<?>, String> myClassToNarrativeName; private HashMap<String, String> myProfileToName;
private HashMap<Class<?>, String> myClassToName;
private HashMap<String, String> myNameToNarrativeTemplate; private HashMap<String, String> myNameToNarrativeTemplate;
private boolean myApplyDefaultDatatypeTemplates=true; private boolean myApplyDefaultDatatypeTemplates = true;
private volatile boolean myInitialized; private volatile boolean myInitialized;
private HashMap<String, String> myNameToTitleTemplate;
@Override @Override
public NarrativeDt generateNarrative(IResource theResource) { public NarrativeDt generateNarrative(IResource theResource) {
return generateNarrative(null, 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 @Override
public NarrativeDt generateNarrative(String theProfile, IResource theResource) { public NarrativeDt generateNarrative(String theProfile, IResource theResource) {
if (!myInitialized) { if (!myInitialized) {
@ -88,10 +171,10 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String name = null; String name = null;
if (StringUtils.isNotBlank(theProfile)) { if (StringUtils.isNotBlank(theProfile)) {
name = myProfileToNarrativeName.get(theProfile); name = myProfileToName.get(theProfile);
} }
if (name == null) { if (name == null) {
name = myClassToNarrativeName.get(theResource.getClass()); name = myClassToName.get(theResource.getClass());
} }
if (name == null) { if (name == null) {
@ -106,7 +189,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
try { try {
Context context = new Context(); Context context = new Context();
context.setVariable("resource", theResource); context.setVariable("resource", theResource);
String result = myProfileTemplateEngine.process(name, context); String result = myProfileTemplateEngine.process(name, context);
if (myCleanWhitespace) { if (myCleanWhitespace) {
@ -131,9 +214,10 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
if (myInitialized) { if (myInitialized) {
return; return;
} }
myProfileToNarrativeName = new HashMap<String, String>(); myProfileToName = new HashMap<String, String>();
myClassToNarrativeName = new HashMap<Class<?>, String>(); myClassToName = new HashMap<Class<?>, String>();
myNameToNarrativeTemplate = new HashMap<String, String>(); myNameToNarrativeTemplate = new HashMap<String, String>();
myNameToTitleTemplate = new HashMap<String, String>();
List<String> propFileName = getPropertyFile(); List<String> propFileName = getPropertyFile();
@ -160,6 +244,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
myProfileTemplateEngine.setDialect(dialect); myProfileTemplateEngine.setDialect(dialect);
myProfileTemplateEngine.initialize(); 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; myInitialized = true;
} }
@ -167,12 +260,9 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
protected abstract List<String> getPropertyFile(); protected abstract List<String> getPropertyFile();
/** /**
* If set to <code>true</code> (which is the default), most whitespace will * If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative before it is returned.
* be trimmed from the generated narrative before it is returned.
* <p> * <p>
* Note that in order to preserve formatting, not all whitespace is trimmed. * 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.
* Repeated whitespace characters (e.g. "\n \n ") will be
* trimmed to a single space.
* </p> * </p>
*/ */
public boolean isCleanWhitespace() { public boolean isCleanWhitespace() {
@ -180,30 +270,24 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
} }
/** /**
* If set to <code>true</code>, which is the default, if any failure occurs * If set to <code>true</code>, which is the default, if any failure occurs during narrative generation the generator will suppress any generated exceptions, and simply return a default narrative
* during narrative generation the generator will suppress any generated * indicating that no narrative is available.
* exceptions, and simply return a default narrative indicating that no
* narrative is available.
*/ */
public boolean isIgnoreFailures() { public boolean isIgnoreFailures() {
return myIgnoreFailures; return myIgnoreFailures;
} }
/** /**
* If set to true, will return an empty narrative block for any profiles * If set to true, will return an empty narrative block for any profiles where no template is available
* where no template is available
*/ */
public boolean isIgnoreMissingTemplates() { public boolean isIgnoreMissingTemplates() {
return myIgnoreMissingTemplates; return myIgnoreMissingTemplates;
} }
/** /**
* If set to <code>true</code> (which is the default), most whitespace will * If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative before it is returned.
* be trimmed from the generated narrative before it is returned.
* <p> * <p>
* Note that in order to preserve formatting, not all whitespace is trimmed. * 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.
* Repeated whitespace characters (e.g. "\n \n ") will be
* trimmed to a single space.
* </p> * </p>
*/ */
public void setCleanWhitespace(boolean theCleanWhitespace) { public void setCleanWhitespace(boolean theCleanWhitespace) {
@ -211,18 +295,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
} }
/** /**
* If set to <code>true</code>, which is the default, if any failure occurs * If set to <code>true</code>, which is the default, if any failure occurs during narrative generation the generator will suppress any generated exceptions, and simply return a default narrative
* during narrative generation the generator will suppress any generated * indicating that no narrative is available.
* exceptions, and simply return a default narrative indicating that no
* narrative is available.
*/ */
public void setIgnoreFailures(boolean theIgnoreFailures) { public void setIgnoreFailures(boolean theIgnoreFailures) {
myIgnoreFailures = theIgnoreFailures; myIgnoreFailures = theIgnoreFailures;
} }
/** /**
* If set to true, will return an empty narrative block for any profiles * If set to true, will return an empty narrative block for any profiles where no template is available
* where no template is available
*/ */
public void setIgnoreMissingTemplates(boolean theIgnoreMissingTemplates) { public void setIgnoreMissingTemplates(boolean theIgnoreMissingTemplates) {
myIgnoreMissingTemplates = theIgnoreMissingTemplates; myIgnoreMissingTemplates = theIgnoreMissingTemplates;
@ -230,7 +311,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
private void loadProperties(String propFileName) throws IOException { private void loadProperties(String propFileName) throws IOException {
ourLog.debug("Loading narrative properties file: {}", propFileName); ourLog.debug("Loading narrative properties file: {}", propFileName);
Properties file = new Properties(); Properties file = new Properties();
InputStream resource = loadResource(propFileName); InputStream resource = loadResource(propFileName);
@ -245,13 +326,22 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String narrativePropName = name + ".narrative"; String narrativePropName = name + ".narrative";
String narrativeName = file.getProperty(narrativePropName); String narrativeName = file.getProperty(narrativePropName);
if (isBlank(narrativeName)) { String titlePropName = name + ".title";
throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' in file " + propFileName); 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)); myProfileToName.put(file.getProperty(nextKey), name);
myProfileToNarrativeName.put(file.getProperty(nextKey), name);
myNameToNarrativeTemplate.put(name, narrative); 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")) { } else if (nextKey.endsWith(".class")) {
@ -262,9 +352,9 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String className = file.getProperty(nextKey); String className = file.getProperty(nextKey);
Class<?> dtClass; Class<?> clazz;
try { try {
dtClass = Class.forName(className); clazz = Class.forName(className);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
ourLog.warn("Unknown datatype class '{}' identified in narrative file {}", name, propFileName); ourLog.warn("Unknown datatype class '{}' identified in narrative file {}", name, propFileName);
continue; continue;
@ -272,16 +362,27 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String narrativePropName = name + ".narrative"; String narrativePropName = name + ".narrative";
String narrativeName = file.getProperty(narrativePropName); String narrativeName = file.getProperty(narrativePropName);
if (isBlank(narrativeName)) { String titlePropName = name + ".title";
throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' in file " + propFileName); 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)); myClassToName.put(clazz, name);
myClassToNarrativeName.put(dtClass, name);
myNameToNarrativeTemplate.put(name, narrative); 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")) { } else if (nextKey.endsWith(".narrative")) {
continue; continue;
} else if (nextKey.endsWith(".title")) {
continue;
} else { } else {
throw new ConfigurationException("Invalid property name: " + nextKey); 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 IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue);
final Object value = expression.execute(configuration, theArguments); final Object value = expression.execute(configuration, theArguments);
theElement.removeAttribute(theAttributeName); theElement.removeAttribute(theAttributeName);
theElement.clearChildren(); theElement.clearChildren();
Context context = new Context(); Context context = new Context();
context.setVariable("resource", value); context.setVariable("resource", value);
String name = myClassToNarrativeName.get(value.getClass()); String name = myClassToName.get(value.getClass());
if (name == null) { if (name == null) {
if (myIgnoreMissingTemplates) { if (myIgnoreMissingTemplates) {
ourLog.debug("No narrative template available for type: {}", value.getClass()); 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)); 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));
}
}
} }

View File

@ -64,4 +64,6 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
return myUseHapiServerConformanceNarrative; return myUseHapiServerConformanceNarrative;
} }
} }

View File

@ -30,4 +30,8 @@ public interface INarrativeGenerator {
NarrativeDt generateNarrative(IResource theResource); NarrativeDt generateNarrative(IResource theResource);
String generateTitle(IResource theResource);
String generateTitle(String theProfile, IResource theResource);
} }

View File

@ -222,6 +222,7 @@ public class JsonParser extends BaseParser implements IParser {
eventWriter.writeEnd(); // entry array eventWriter.writeEnd(); // entry array
eventWriter.writeEnd(); eventWriter.writeEnd();
eventWriter.flush();
eventWriter.close(); eventWriter.close();
} }
@ -506,6 +507,7 @@ public class JsonParser extends BaseParser implements IParser {
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
encodeResourceToJsonStreamWriter(resDef, theResource, eventWriter, null,false); encodeResourceToJsonStreamWriter(resDef, theResource, eventWriter, null,false);
eventWriter.flush(); eventWriter.flush();
eventWriter.close();
} }
@Override @Override
@ -536,6 +538,7 @@ public class JsonParser extends BaseParser implements IParser {
eventWriter.writeEnd(); eventWriter.writeEnd();
eventWriter.flush(); eventWriter.flush();
eventWriter.close();
} }
/** /**

View File

@ -417,6 +417,9 @@ class ParserState<T> {
if (myEntry.getUpdated().isEmpty() == false) { if (myEntry.getUpdated().isEmpty() == false) {
ResourceMetadataKeyEnum.UPDATED.put(myEntry.getResource(), myEntry.getUpdated()); ResourceMetadataKeyEnum.UPDATED.put(myEntry.getResource(), myEntry.getUpdated());
} }
ResourceMetadataKeyEnum.TITLE.put(myEntry.getResource(), myEntry.getTitle().getValue());
if (myEntry.getCategories().isEmpty() == false) { if (myEntry.getCategories().isEmpty() == false) {
TagList tagList = new TagList(); TagList tagList = new TagList();
for (Tag next : myEntry.getCategories()) { for (Tag next : myEntry.getCategories()) {
@ -608,7 +611,6 @@ class ParserState<T> {
myPreResourceState = thePreResourceState; myPreResourceState = thePreResourceState;
} }
@SuppressWarnings("unused")
public void attributeValue(String theName, String theValue) throws DataFormatException { public void attributeValue(String theName, String theValue) throws DataFormatException {
// ignore by default // ignore by default
} }
@ -617,7 +619,6 @@ class ParserState<T> {
// ignore by default // ignore by default
} }
@SuppressWarnings("unused")
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
// ignore by default // ignore by default
} }
@ -657,7 +658,7 @@ class ParserState<T> {
myStack = theState; myStack = theState;
} }
public void string(@SuppressWarnings("unused") String theData) { public void string(String theData) {
// ignore by default // ignore by default
} }
@ -665,7 +666,7 @@ class ParserState<T> {
// allow an implementor to override // allow an implementor to override
} }
public void xmlEvent(@SuppressWarnings("unused") XMLEvent theNextEvent) { public void xmlEvent(XMLEvent theNextEvent) {
// ignore // ignore
} }

View File

@ -31,7 +31,7 @@ import ca.uhn.fhir.rest.method.QualifiedParamList;
public class IdentifierListParam implements IQueryParameterOr { public class IdentifierListParam implements IQueryParameterOr {
private List<IdentifierDt> myIdentifiers = new ArrayList<IdentifierDt>(); private List<IdentifierDt> myIdentifiers = new ArrayList<IdentifierDt>();
/** /**
* Returns all identifiers associated with this list * 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);
}
}
} }

View File

@ -160,6 +160,8 @@ public class SearchParameter extends BaseQueryParameter {
myParamType = SearchParamTypeEnum.QUANTITY; myParamType = SearchParamTypeEnum.QUANTITY;
} else if (ReferenceParam.class.isAssignableFrom(type)) { } else if (ReferenceParam.class.isAssignableFrom(type)) {
myParamType = SearchParamTypeEnum.REFERENCE; myParamType = SearchParamTypeEnum.REFERENCE;
} else if (IdentifierListParam.class.isAssignableFrom(type)) {
myParamType = SearchParamTypeEnum.TOKEN;
} else { } else {
throw new ConfigurationException("Unknown search parameter type: " + type); throw new ConfigurationException("Unknown search parameter type: " + type);
} }

View File

@ -45,6 +45,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
@ -557,7 +558,7 @@ public class RestfulServer extends HttpServlet {
statusCode=((BaseServerResponseException) e).getStatusCode(); statusCode=((BaseServerResponseException) e).getStatusCode();
issue.getDetails().setValue(e.getMessage()); issue.getDetails().setValue(e.getMessage());
} else { } 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)); issue.getDetails().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
} }
@ -779,6 +780,14 @@ public class RestfulServer extends HttpServlet {
bundle.getLinkSelf().setValue(theCompleteUrl); bundle.getLinkSelf().setValue(theCompleteUrl);
for (IResource next : theResult) { 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); bundle.addResource(next, theContext, theServerBase);
} }
@ -1019,6 +1028,7 @@ public class RestfulServer extends HttpServlet {
RestfulServer.getNewParser(theServer.getFhirContext(), theResponseEncoding, thePrettyPrint, theNarrativeMode).encodeBundleToWriter(bundle, writer); RestfulServer.getNewParser(theServer.getFhirContext(), theResponseEncoding, thePrettyPrint, theNarrativeMode).encodeBundleToWriter(bundle, writer);
} }
} finally { } finally {
writer.flush();
writer.close(); writer.close();
} }
} }
@ -1093,6 +1103,7 @@ public class RestfulServer extends HttpServlet {
RestfulServer.getNewParser(theServer.getFhirContext(), theResponseEncoding, thePrettyPrint, theNarrativeMode).encodeResourceToWriter(theResource, writer); RestfulServer.getNewParser(theServer.getFhirContext(), theResponseEncoding, thePrettyPrint, theNarrativeMode).encodeResourceToWriter(theResource, writer);
} }
} finally { } finally {
writer.flush();
writer.close(); writer.close();
} }
} }

View File

@ -32,6 +32,9 @@ quantity.narrative=classpath:ca/uhn/fhir/narrative/QuantityDt.html
patient.class=ca.uhn.fhir.model.dstu.resource.Patient patient.class=ca.uhn.fhir.model.dstu.resource.Patient
patient.narrative=classpath:ca/uhn/fhir/narrative/Patient.html 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.class=ca.uhn.fhir.model.dstu.resource.DiagnosticReport
diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html
diagnosticreport.title=classpath:ca/uhn/fhir/narrative/title/DiagnosticReport.html

View File

@ -0,0 +1,9 @@
<div>
<th:block th:if="${not resource.name.text.empty}" th:text="${resource.name.text.value}"/>
<th:block th:if=" ${resource.name.text.empty} and ${not resource.name.codingFirstRep.display.empty}" th:text="${resource.name.codingFirstRep.display}"/>
<th:block th:if= "${resource.name.text.empty} and ${resource.name.codingFirstRep.display.empty}" th:text="Untitled Diagnostic Report"/>
<th:block th:if="${not resource.status.empty}" th:text="' - ' + ${resource.status.value}"/>
<th:block th:text="' - ' + ${resource.result.size} + ' observations'"/>
</div>

View File

@ -0,0 +1,9 @@
<div>
<th:block th:each="prefix : ${resource.nameFirstRep.prefix}" th:text="${prefix.value} + ' '">Dr</th:block>
<th:block th:each="givenName : ${resource.nameFirstRep.given}" th:text="${givenName.value} + ' '">John</th:block>
<th:block th:each="familyName : ${resource.nameFirstRep.family}" th:text="${#strings.toUpperCase(familyName.value)} + ' '">SMITH</th:block>
<th:block th:each="suffix : ${resource.nameFirstRep.suffix}" th:text="${suffix.value} + ' '">Jr</th:block>
<th:block th:if="${not resource.identifierFirstRep.empty}">
(<th:block th:text="${resource.identifierFirstRep.value.value}">8708660</th:block>)
</th:block>
</div>

View File

@ -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());
}
}

View File

@ -28,4 +28,14 @@ public class IdentifierDtTest {
assertEquals("|b|c", dt.getValueAsQueryToken()); 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());
}
} }

View File

@ -46,9 +46,12 @@ public class DefaultThymeleafNarrativeGeneratorTest {
value.setBirthDate(new Date(), TemporalPrecisionEnum.DAY); value.setBirthDate(new Date(), TemporalPrecisionEnum.DAY);
String output = gen.generateNarrative("http://hl7.org/fhir/profiles/Patient", value).getDiv().getValueAsString(); String output = gen.generateNarrative(value).getDiv().getValueAsString();
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> joe john <b>BLOW </b></div>"));
ourLog.info(output);
String title = gen.generateTitle(value);
assertEquals("joe john BLOW (123456)", title);
ourLog.info(title);
} }
@Test @Test
@ -105,6 +108,11 @@ public class DefaultThymeleafNarrativeGeneratorTest {
ourLog.info(output); ourLog.info(output);
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> Some Diagnostic Report </div>")); assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> Some Diagnostic Report </div>"));
String title = gen.generateTitle(value);
ourLog.info(title);
assertEquals("Some Diagnostic Report - final - 2 observations", title);
// Now try it with the parser // Now try it with the parser
FhirContext context = new FhirContext(); FhirContext context = new FhirContext();

View File

@ -23,9 +23,8 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Patient; 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.OptionalParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.testutil.RandomServerPortProvider; import ca.uhn.fhir.testutil.RandomServerPortProvider;
@ -36,7 +35,6 @@ import ca.uhn.fhir.testutil.RandomServerPortProvider;
public class SearchTest { public class SearchTest {
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchTest.class);
private static int ourPort; private static int ourPort;
private static Server ourServer; private static Server ourServer;
private static FhirContext ourCtx = new FhirContext(); private static FhirContext ourCtx = new FhirContext();
@ -46,12 +44,14 @@ public class SearchTest {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa");
HttpResponse status = ourClient.execute(httpGet); HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent()); String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
assertEquals(1, bundle.getEntries().size()); assertEquals(1, bundle.getEntries().size());
Patient p = bundle.getResources(Patient.class).get(0); Patient p = bundle.getResources(Patient.class).get(0);
assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString()); assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString());
assertEquals("IDAAA (identifier123)", bundle.getEntries().get(0).getTitle().getValue());
} }
@AfterClass @AfterClass
@ -68,6 +68,8 @@ public class SearchTest {
ServletHandler proxyHandler = new ServletHandler(); ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(); RestfulServer servlet = new RestfulServer();
servlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
servlet.setResourceProviders(patientProvider); servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet); ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*"); proxyHandler.addServletWithMapping(servletHolder, "/*");
@ -92,6 +94,7 @@ public class SearchTest {
Patient patient = new Patient(); Patient patient = new Patient();
patient.setId("1"); patient.setId("1");
patient.addIdentifier("system", "identifier123");
patient.addName().addFamily("id"+theParam.getValue()); patient.addName().addFamily("id"+theParam.getValue());
retVal.add(patient); retVal.add(patient);
return retVal; return retVal;

View File

@ -747,6 +747,9 @@ public abstract class BaseFhirDao {
} }
} }
String title = ResourceMetadataKeyEnum.TITLE.get(theResource);
theEntity.setTitle(title);
} }
protected ResourceTable toEntity(IResource theResource) { protected ResourceTable toEntity(IResource theResource) {
@ -784,6 +787,10 @@ public abstract class BaseFhirDao {
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, theEntity.getPublished()); retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, theEntity.getPublished());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, theEntity.getUpdated()); retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, theEntity.getUpdated());
if (theEntity.getTitle()!=null) {
ResourceMetadataKeyEnum.TITLE.put(retVal, theEntity.getTitle());
}
if (theEntity.getDeleted()!=null) { if (theEntity.getDeleted()!=null) {
ResourceMetadataKeyEnum.DELETED_AT.put(retVal, new InstantDt(theEntity.getDeleted())); ResourceMetadataKeyEnum.DELETED_AT.put(retVal, new InstantDt(theEntity.getDeleted()));
} }

View File

@ -431,7 +431,10 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
ArrayList<Predicate> singleCodePredicates = (new ArrayList<Predicate>()); ArrayList<Predicate> singleCodePredicates = (new ArrayList<Predicate>());
if (StringUtils.isNotBlank(system)) { if (StringUtils.isNotBlank(system)) {
singleCodePredicates.add(builder.equal(from.get("mySystem"), system)); singleCodePredicates.add(builder.equal(from.get("mySystem"), system));
} else if (system == null) {
// don't check the system
} else { } else {
// If the system is "", we only match on null systems
singleCodePredicates.add(builder.isNull(from.get("mySystem"))); singleCodePredicates.add(builder.isNull(from.get("mySystem")));
} }
if (StringUtils.isNotBlank(code)) { if (StringUtils.isNotBlank(code)) {

View File

@ -35,6 +35,17 @@ public class SearchParameterMap extends HashMap<String, List<List<IQueryParamete
} }
} }
public void add(String theName, IQueryParameterOr theOr) {
if (theOr == null) {
return;
}
if (!containsKey(theName)) {
put(theName, new ArrayList<List<IQueryParameterType>>());
}
get(theName).add(theOr.getValuesAsQueryTokens());
}
public void add(String theName, IQueryParameterType theParam) { public void add(String theName, IQueryParameterType theParam) {
if (theParam == null) { if (theParam == null) {
return; return;

View File

@ -17,6 +17,8 @@ import ca.uhn.fhir.model.primitive.InstantDt;
@MappedSuperclass @MappedSuperclass
public abstract class BaseHasResource { public abstract class BaseHasResource {
private static final int MAX_TITLE_LENGTH = 100;
@Column(name = "RES_DELETED_AT", nullable = true) @Column(name = "RES_DELETED_AT", nullable = true)
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date myDeleted; private Date myDeleted;
@ -33,6 +35,9 @@ public abstract class BaseHasResource {
@Lob() @Lob()
private byte[] myResource; private byte[] myResource;
@Column(name = "RES_TITLE", nullable = true, length = MAX_TITLE_LENGTH)
private String myTitle;
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
@Column(name = "RES_UPDATED", nullable = false) @Column(name = "RES_UPDATED", nullable = false)
private Date myUpdated; private Date myUpdated;
@ -61,6 +66,10 @@ public abstract class BaseHasResource {
public abstract Collection<? extends BaseTag> getTags(); public abstract Collection<? extends BaseTag> getTags();
public String getTitle() {
return myTitle;
}
public InstantDt getUpdated() { public InstantDt getUpdated() {
return new InstantDt(myUpdated); return new InstantDt(myUpdated);
} }
@ -87,6 +96,10 @@ public abstract class BaseHasResource {
myResource = theResource; myResource = theResource;
} }
public void setTitle(String theTitle) {
myTitle = theTitle;
}
public void setUpdated(Date theUpdated) { public void setUpdated(Date theUpdated) {
myUpdated = theUpdated; myUpdated = theUpdated;
} }

View File

@ -8,7 +8,10 @@ import org.apache.commons.lang3.StringUtils;
@Entity @Entity
@Table(name = "HFJ_SPIDX_TOKEN" /* , indexes = { @Index(name = "IDX_SP_TOKEN", columnList = "SP_SYSTEM,SP_VALUE") } */) @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 { public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -252,6 +252,7 @@ public class ResourceTable extends BaseHasResource implements Serializable {
retVal.setResourceType(myResourceType); retVal.setResourceType(myResourceType);
retVal.setVersion(myVersion); retVal.setVersion(myVersion);
retVal.setTitle(getTitle());
retVal.setPublished(getPublished()); retVal.setPublished(getPublished());
retVal.setUpdated(getUpdated()); retVal.setUpdated(getUpdated());
retVal.setEncoding(getEncoding()); retVal.setEncoding(getEncoding());

View File

@ -39,7 +39,9 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; 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.DateRangeParam;
import ca.uhn.fhir.rest.param.IdentifierListParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam; import ca.uhn.fhir.rest.param.QualifiedDateParam;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
@ -122,6 +124,50 @@ public class FhirResourceDaoTest {
assertEquals(o1id.toUnqualifiedVersionless(), p1.getManagingOrganization().getReference().toUnqualifiedVersionless()); 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 @Test
public void testIdParam() { public void testIdParam() {
Patient patient = new Patient(); Patient patient = new Patient();
@ -360,6 +406,7 @@ public class FhirResourceDaoTest {
Patient patient = new Patient(); Patient patient = new Patient();
patient.addIdentifier("urn:system", "001"); patient.addIdentifier("urn:system", "001");
patient.addName().addFamily("testSearchNameParam01Fam").addGiven("testSearchNameParam01Giv"); patient.addName().addFamily("testSearchNameParam01Fam").addGiven("testSearchNameParam01Giv");
ResourceMetadataKeyEnum.TITLE.put(patient, "P1TITLE");
id1 = ourPatientDao.create(patient).getId(); id1 = ourPatientDao.create(patient).getId();
} }
{ {
@ -374,6 +421,7 @@ public class FhirResourceDaoTest {
List<Patient> patients = toList(ourPatientDao.search(params)); List<Patient> patients = toList(ourPatientDao.search(params));
assertEquals(1, patients.size()); assertEquals(1, patients.size());
assertEquals(id1.getIdPart(), patients.get(0).getId().getIdPart()); assertEquals(id1.getIdPart(), patients.get(0).getId().getIdPart());
assertEquals("P1TITLE", ResourceMetadataKeyEnum.TITLE.get(patients.get(0)));
// Given name shouldn't return for family param // Given name shouldn't return for family param
params = new HashMap<String, IQueryParameterType>(); params = new HashMap<String, IQueryParameterType>();

View File

@ -1,35 +1,52 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<div th:fragment="banner" class="well"> <div th:fragment="banner" class="well">
<th:block th:if="${serverEntry.key} == 'home'">
<p>
This is the home for the FHIR test server operated by
<a href="http://uhn.ca">University Health Network</a>. This server
(and the testing application you are currently using to access it)
is entirely built using
<a href="https://github.com/jamesagnew/hapi-fhir">HAPI-FHIR</a>,
a 100% open-source Java implementation of the
<a href="http://hl7.org/implement/standards/fhir/">FHIR specification</a>.
</p>
<p>
Here are some things you might wish to try:
</p>
<ul>
<li>
View a
<a href="http://fhirtest.uhn.ca/search?serverId=home&amp;encoding=json&amp;pretty=true&amp;resource=Patient&amp;param.0.type=string&amp;param.0.name=_id&amp;param.0.0=&amp;resource-search-limit=">list of patients</a>
on this server.
</li>
<li>
Construct a
<a href="http://fhirtest.uhn.ca/resource?serverId=home&amp;encoding=json&amp;pretty=true&amp;resource=Patient">search query</a>
on this server.
</li>
<li>
Access a
<a href="http://fhirtest.uhn.ca/home?serverId=furore">different server</a>
(use the <b>Server</b> menu at the top of the page to see a list of public FHIR servers)
</li>
</ul>
</th:block>
<th:block th:if="${serverEntry.key} != 'home'">
<p>
You are accessing the public FHIR server
<b th:text="${baseName}"/>. This server is hosted elsewhere on the internet
but is being accessed using
</p>
</th:block>
<p> <p>
This is the home for the FHIR test server operated by <b style="color: red;">
<a href="http://uhn.ca">University Health Network</a>. <span class="glyphicon glyphicon-warning-sign/>
</p> This is not a production server!
<p> </b>
<b style="color: red;">This is not a production server!</b>
Do not store any information here that contains personal health information Do not store any information here that contains personal health information
or otherwise confidential information. This server will be regularly purged or any other confidential information. This server will be regularly purged
and reloaded with fixed test data. and reloaded with fixed test data.
</p> </p>
<p>
Here are some things you might wish to try:
</p>
<ul>
<li>
View a
<a href="http://fhirtest.uhn.ca/search?serverId=home&amp;encoding=json&amp;pretty=true&amp;resource=Patient&amp;param.0.type=string&amp;param.0.name=_id&amp;param.0.0=&amp;resource-search-limit=">list of patients</a>
on this server.
</li>
<li>
Construct a
<a href="http://fhirtest.uhn.ca/resource?serverId=home&amp;encoding=json&amp;pretty=true&amp;resource=Patient">search query</a>
on this server.
</li>
<li>
Access a
<a href="http://fhirtest.uhn.ca/home?serverId=furore">different server</a>
(use the <b>Server</b> menu at the top of the page to see a list of public FHIR servers)
</li>
</ul>
</div> </div>
</html> </html>

View File

@ -11,6 +11,7 @@
<bean class="ca.uhn.fhir.to.TesterConfig"> <bean class="ca.uhn.fhir.to.TesterConfig">
<property name="servers"> <property name="servers">
<list> <list>
<value>test, TEST, http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/</value>
<value>home , Localhost Server , http://localhost:8887/fhir/context </value> <value>home , Localhost Server , http://localhost:8887/fhir/context </value>
<value>hi , Health Intersections , http://fhir.healthintersections.com.au/open</value> <value>hi , Health Intersections , http://fhir.healthintersections.com.au/open</value>
<value>furore , Spark - Furore Reference Server , http://spark.furore.com/fhir</value> <value>furore , Spark - Furore Reference Server , http://spark.furore.com/fhir</value>

View File

@ -34,7 +34,7 @@ public class ${className}ResourceProvider extends JpaResourceProvider<${classNam
#if (${param.type} == 'string' ) #if (${param.type} == 'string' )
StringParam the${param.nameCapitalized}, StringParam the${param.nameCapitalized},
#elseif (${param.type} == 'token' ) #elseif (${param.type} == 'token' )
IdentifierDt the${param.nameCapitalized}, IdentifierListParam the${param.nameCapitalized},
#elseif (${param.type} == 'date' ) #elseif (${param.type} == 'date' )
DateRangeParam the${param.nameCapitalized}, DateRangeParam the${param.nameCapitalized},
#elseif (${param.type} == 'quantity' ) #elseif (${param.type} == 'quantity' )