Merge branch 'master' of github.com:jamesagnew/hapi-fhir

Conflicts:
	hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-server-database-config.xml
	hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html
This commit is contained in:
jamesagnew 2014-07-03 18:35:37 -04:00
commit ee53c40ff8
49 changed files with 869 additions and 169 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,7 +222,7 @@ public class JsonParser extends BaseParser implements IParser {
eventWriter.writeEnd(); // entry array eventWriter.writeEnd(); // entry array
eventWriter.writeEnd(); eventWriter.writeEnd();
eventWriter.close(); eventWriter.flush();
} }
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theWriter, IElement theValue, BaseRuntimeElementDefinition<?> theChildDef, String theChildName) throws IOException { private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theWriter, IElement theValue, BaseRuntimeElementDefinition<?> theChildDef, String theChildName) throws IOException {

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

@ -20,11 +20,10 @@ package ca.uhn.fhir.rest.server;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.*; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.io.Writer; import java.io.Writer;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -46,8 +45,9 @@ 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.lang3.Validate; 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.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
@ -542,22 +542,26 @@ public class RestfulServer extends HttpServlet {
theResponse.setContentType("text/plain"); theResponse.setContentType("text/plain");
theResponse.setCharacterEncoding("UTF-8"); theResponse.setCharacterEncoding("UTF-8");
theResponse.getWriter().write(e.getMessage()); theResponse.getWriter().write(e.getMessage());
} catch (Throwable e) { } catch (Throwable e) {
int statusCode = 500;
if (e instanceof InternalErrorException) {
ourLog.error("Failure during REST processing", e);
} else if (e instanceof BaseServerResponseException) {
ourLog.warn("Failure during REST processing: {}", e.toString());
statusCode=((BaseServerResponseException) e).getStatusCode();
} else {
ourLog.warn("Failure during REST processing: {}", e.toString());
}
OperationOutcome oo = new OperationOutcome(); OperationOutcome oo = new OperationOutcome();
Issue issue = oo.addIssue(); Issue issue = oo.addIssue();
issue.getSeverity().setValueAsEnum(IssueSeverityEnum.ERROR); issue.getSeverity().setValueAsEnum(IssueSeverityEnum.ERROR);
issue.getDetails().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
int statusCode = 500;
if (e instanceof InternalErrorException) {
ourLog.error("Failure during REST processing", e);
issue.getDetails().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
} else if (e instanceof BaseServerResponseException) {
ourLog.warn("Failure during REST processing: {}", e.toString());
statusCode=((BaseServerResponseException) e).getStatusCode();
issue.getDetails().setValue(e.getMessage());
} else {
ourLog.error("Failure during REST processing: {}"+ e.toString(), e);
issue.getDetails().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
}
streamResponseAsResource(this, theResponse, oo, determineResponseEncoding(theRequest), true, false, NarrativeModeEnum.NORMAL, statusCode,false); streamResponseAsResource(this, theResponse, oo, determineResponseEncoding(theRequest), true, false, NarrativeModeEnum.NORMAL, statusCode,false);
@ -776,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);
} }

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

@ -43,28 +43,44 @@ public class ExceptionTest {
private static RestfulServer servlet; private static RestfulServer servlet;
@Test @Test
public void testSearchNormalMatch() throws Exception { public void testInternalError() throws Exception {
{ {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?throwInternalError=aaa"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?throwInternalError=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());
ourLog.info(responseContent); ourLog.info(responseContent);
assertEquals(500, status.getStatusLine().getStatusCode()); assertEquals(500, status.getStatusLine().getStatusCode());
OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newXmlParser().parseResource(responseContent); OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newXmlParser().parseResource(responseContent);
assertThat(oo.getIssueFirstRep().getDetails().getValue(), StringContains.containsString("InternalErrorException: Exception Text")); assertThat(oo.getIssueFirstRep().getDetails().getValue(), StringContains.containsString("InternalErrorException: Exception Text"));
} }
}
@Test
public void testInternalErrorFormatted() throws Exception {
{ {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?throwInternalError=aaa&_format=json"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?throwInternalError=aaa&_format=true");
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());
ourLog.info(responseContent); ourLog.info(responseContent);
assertEquals(500, status.getStatusLine().getStatusCode()); assertEquals(500, status.getStatusLine().getStatusCode());
OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newJsonParser().parseResource(responseContent); OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newXmlParser().parseResource(responseContent);
assertThat(oo.getIssueFirstRep().getDetails().getValue(), StringContains.containsString("InternalErrorException: Exception Text")); assertThat(oo.getIssueFirstRep().getDetails().getValue(), StringContains.containsString("InternalErrorException: Exception Text"));
} }
} }
@Test
public void testInternalErrorJson() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?throwInternalError=aaa&_format=json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(500, status.getStatusLine().getStatusCode());
OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newJsonParser().parseResource(responseContent);
assertThat(oo.getIssueFirstRep().getDetails().getValue(), StringContains.containsString("InternalErrorException: Exception Text"));
}
@AfterClass @AfterClass

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

@ -7,11 +7,13 @@ import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.Conformance.Rest; import ca.uhn.fhir.model.dstu.resource.Conformance.Rest;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource; import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource;
import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider; import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider;
public class JpaConformanceProvider extends ServerConformanceProvider { public class JpaConformanceProvider extends ServerConformanceProvider {
private String myImplementationDescription;
private IFhirSystemDao mySystemDao; private IFhirSystemDao mySystemDao;
public JpaConformanceProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao) { public JpaConformanceProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao) {
@ -19,24 +21,29 @@ public class JpaConformanceProvider extends ServerConformanceProvider {
mySystemDao = theSystemDao; mySystemDao = theSystemDao;
super.setCache(false); super.setCache(false);
} }
@Override @Override
public Conformance getServerConformance() { public Conformance getServerConformance() {
Map<String, Long> counts = mySystemDao.getResourceCounts(); Map<String, Long> counts = mySystemDao.getResourceCounts();
Conformance retVal = super.getServerConformance(); Conformance retVal = super.getServerConformance();
for (Rest nextRest : retVal.getRest()) { for (Rest nextRest : retVal.getRest()) {
for (RestResource nextResource : nextRest.getResource()) { for (RestResource nextResource : nextRest.getResource()) {
Long count = counts.get(nextResource.getType().getValueAsString()); Long count = counts.get(nextResource.getType().getValueAsString());
if (count!=null) { if (count != null) {
nextResource.addUndeclaredExtension(false, "http://hl7api.sourceforge.net/hapi-fhir/res/extdefs.html#resourceCount", new DecimalDt(count)); nextResource.addUndeclaredExtension(false, "http://hl7api.sourceforge.net/hapi-fhir/res/extdefs.html#resourceCount", new DecimalDt(count));
} }
} }
} }
retVal.getImplementation().setDescription(myImplementationDescription);
return retVal; return retVal;
} }
public void setImplementationDescription(String theImplDesc) {
myImplementationDescription = theImplDesc;
}
} }

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();
@ -253,13 +299,12 @@ public class FhirResourceDaoTest {
@Test @Test
public void testSearchWithNoResults() { public void testSearchWithNoResults() {
IBundleProvider value = ourObservationDao.search(new SearchParameterMap()); IBundleProvider value = ourDeviceDao.search(new SearchParameterMap());
for (IResource next : value.getResources(0, value.size())) {
ourDeviceDao.delete(next.getId());
}
/* value = ourDeviceDao.search(new SearchParameterMap());
* This may fail at some point, which means another test has probably added a device
* resource. This test depends on there being none, so if that happens this test
* should be refactored to use another resource type
*/
assertEquals(0, value.size()); assertEquals(0, value.size());
List<IResource> res = value.getResources(0, 0); List<IResource> res = value.getResources(0, 0);
@ -361,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();
} }
{ {
@ -375,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

@ -148,6 +148,31 @@
<target>1.7</target> <target>1.7</target>
</configuration> </configuration>
</plugin> </plugin>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId></groupId>
<artifactId></artifactId>
<versionRange>[0.4,)</versionRange>
<goals>
<goal></goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
<plugins> <plugins>

View File

@ -55,7 +55,10 @@ public class TestRestfulServer extends RestfulServer {
JpaSystemProvider sp = new JpaSystemProvider(systemDao); JpaSystemProvider sp = new JpaSystemProvider(systemDao);
setPlainProviders(sp); setPlainProviders(sp);
String implDesc = getInitParameter("ImplementationDescription");
JpaConformanceProvider confProvider = new JpaConformanceProvider(this, systemDao); JpaConformanceProvider confProvider = new JpaConformanceProvider(this, systemDao);
confProvider.setImplementationDescription(implDesc);
setServerConformanceProvider(confProvider); setServerConformanceProvider(confProvider);
setUseBrowserFriendlyContentTypes(true); setUseBrowserFriendlyContentTypes(true);

View File

@ -31,6 +31,7 @@
<!-- <property name="url" value="jdbc:derby:directory:#{systemproperties['fhir.db.location']};create=true" /> --> <!-- <property name="url" value="jdbc:derby:directory:#{systemproperties['fhir.db.location']};create=true" /> -->
<property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver"></property> <property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver"></property>
<property name="url" value="jdbc:derby://localhost:1527//opt/glassfish/fhirtest/fhirtest;create=true" /> <property name="url" value="jdbc:derby://localhost:1527//opt/glassfish/fhirtest/fhirtest;create=true" />
<!--<property name="url" value="jdbc:derby://localhost:1527#{systemProperties['fhir.db.location']};create=true" />-->
<property name="username" value="SA"/> <property name="username" value="SA"/>
<property name="password" value="SA"/> <property name="password" value="SA"/>
</bean> </bean>

View File

@ -0,0 +1,56 @@
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!--
<bean class="ca.uhn.fhirtest.HsqldbServer" id="dbServer" init-method="start">
<constructor-arg>
<value>
server.database.0=file:#{systemProperties['fhir.db.location']}/hsql-fhir-db
server.dbname.0=uhnfhirdb
server.remote_open=true
hsqldb.reconfig_logging=false
hsqldb.default_table_type=cached
</value>
</constructor-arg>
</bean>
-->
<bean id="dbServer" class="ca.uhn.fhirtest.DerbyNetworkServer">
</bean>
<bean depends-on="dbServer" id="myPersistenceDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- ;create=true /opt/glassfish/glassfish4/glassfish/nodes/localhost-domain1/fhirtest/fhirdb -->
<!-- <property name="url" value="jdbc:hsqldb:hsql://localhost/uhnfhirdb"/>-->
<!-- <property name="url" value="jdbc:derby:directory:#{systemproperties['fhir.db.location']};create=true" /> -->
<property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver"></property>
<<<<<<< HEAD
<property name="url" value="jdbc:derby://localhost:1527//opt/glassfish/fhirtest/fhirtest;create=true" />
=======
<property name="url" value="jdbc:derby://localhost:1527#{systemProperties['fhir.db.location']};create=true" />
>>>>>>> ca0929df07b8d435a3b756f89998f68e2aae79fc
<property name="username" value="SA"/>
<property name="password" value="SA"/>
</bean>
<bean depends-on="dbServer" id="myEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="myPersistenceDataSource" />
<property name="persistenceXmlLocation" value="classpath:META-INF/fhirtest_persistence.xml" />
<property name="persistenceUnitName" value="FHIR_UT" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="generateDdl" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.DerbyTenSevenDialect" />
<!-- <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect" />-->
</bean>
</property>
</bean>
</beans>

View File

@ -1,14 +1,31 @@
<!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="${serverId} == '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>
</th:block>
<th:block th:if="${serverId} != '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 the HAPI client implementation.
</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> <p>

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<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>
<b style="color: red;">
<span class="glyphicon glyphicon-warning-sign/>
This is not a production server!
</b>
Do not store any information here that contains personal health information
or any other confidential information. This server will be regularly purged
and reloaded with fixed test data.
</p>
<<<<<<< HEAD
<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>
=======
>>>>>>> ca0929df07b8d435a3b756f89998f68e2aae79fc
</div>
</html>

View File

@ -35,6 +35,10 @@
<servlet> <servlet>
<servlet-name>fhirServlet</servlet-name> <servlet-name>fhirServlet</servlet-name>
<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class> <servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
<init-param>
<param-name>ImplementationDescription</param-name>
<param-value>UHN Test Server</param-value>
</init-param>
<load-on-startup>1</load-on-startup> <load-on-startup>1</load-on-startup>
</servlet> </servlet>

View File

@ -75,8 +75,6 @@
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" <xsd:import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="xml.xsd"/> schemaLocation="xml.xsd"/>
<xsd:include schemaLocation="javaee_web_services_client_1_3.xsd"/>
<xsd:group name="descriptionGroup"> <xsd:group name="descriptionGroup">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>

View File

@ -23,7 +23,7 @@ public class UhnFhirTestApp {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
// new File("target/testdb").mkdirs(); // new File("target/testdb").mkdirs();
System.setProperty("fhir.db.location", "target/testdb"); System.setProperty("fhir.db.location", "/target/testdb");
int myPort = 8888; int myPort = 8888;
Server server = new Server(myPort); Server server = new Server(myPort);

View File

@ -3,7 +3,6 @@
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/> <wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/> <wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/> <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<dependent-module archiveName="hapi-fhir-base-0.4-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base"> <dependent-module archiveName="hapi-fhir-base-0.4-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
<dependency-type>uses</dependency-type> <dependency-type>uses</dependency-type>
</dependent-module> </dependent-module>

View File

@ -31,10 +31,10 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version> <version>3.1.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -50,6 +50,7 @@ import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.to.model.HomeRequest; import ca.uhn.fhir.to.model.HomeRequest;
import ca.uhn.fhir.to.model.ResourceRequest; import ca.uhn.fhir.to.model.ResourceRequest;
import ca.uhn.fhir.to.model.TransactionRequest;
@org.springframework.stereotype.Controller() @org.springframework.stereotype.Controller()
public class Controller { public class Controller {
@ -67,6 +68,39 @@ public class Controller {
@Autowired @Autowired
private TemplateEngine myTemplateEngine; private TemplateEngine myTemplateEngine;
@RequestMapping(value = { "/transaction" })
public String actionTransaction(final TransactionRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) {
addCommonParams(theRequest, theModel);
GenericClient client = theRequest.newClient(myCtx, myConfig);
String body = preProcessMessageBody(theRequest.getTransactionBody());
Bundle bundle;
try {
if (body.startsWith("{")) {
bundle = myCtx.newJsonParser().parseBundle(body);
} else if (body.startsWith("<")) {
bundle = myCtx.newXmlParser().parseBundle(body);
} else {
theModel.put("errorMsg", "Message body does not appear to be a valid FHIR resource instance document. Body should start with '<' (for XML encoding) or '{' (for JSON encoding).");
return "home";
}
} catch (DataFormatException e) {
ourLog.warn("Failed to parse bundle", e);
theModel.put("errorMsg", "Failed to parse transaction bundle body. Error was: " + e.getMessage());
return "home";
}
long start = System.currentTimeMillis();
// client.tr
long delay = System.currentTimeMillis() - start;
processAndAddLastClientInvocation(client, ResultType.RESOURCE, theModel, delay, "Loaded conformance");
return "result";
}
@RequestMapping(value = { "/conformance" }) @RequestMapping(value = { "/conformance" })
public String actionConformance(final HomeRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) { public String actionConformance(final HomeRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) {
addCommonParams(theRequest, theModel); addCommonParams(theRequest, theModel);
@ -427,18 +461,7 @@ public class Controller {
return; return;
} }
body = body.trim(); body = preProcessMessageBody(body);
StringBuilder b = new StringBuilder();
for (int i = 0; i < body.length(); i++) {
char nextChar = body.charAt(i);
int nextCharI = nextChar;
if (nextCharI == 65533) {
continue;
}
b.append(nextChar);
}
body = b.toString();
IResource resource; IResource resource;
try { try {
@ -485,6 +508,25 @@ public class Controller {
} }
private String preProcessMessageBody(String theBody) {
if(theBody==null) {
return "";
}
String retVal = theBody.trim();
StringBuilder b = new StringBuilder();
for (int i = 0; i < retVal.length(); i++) {
char nextChar = retVal.charAt(i);
int nextCharI = nextChar;
if (nextCharI == 65533) {
continue;
}
b.append(nextChar);
}
retVal = b.toString();
return retVal;
}
private void doActionHistory(HttpServletRequest theReq, HomeRequest theRequest, BindingResult theBindingResult, ModelMap theModel, String theMethod, String theMethodDescription) { private void doActionHistory(HttpServletRequest theReq, HomeRequest theRequest, BindingResult theBindingResult, ModelMap theModel, String theMethod, String theMethodDescription) {
addCommonParams(theRequest, theModel); addCommonParams(theRequest, theModel);

View File

@ -0,0 +1,18 @@
package ca.uhn.fhir.to.model;
import org.springframework.web.bind.annotation.ModelAttribute;
public class TransactionRequest extends HomeRequest {
private String myTransactionBody;
@ModelAttribute("transactionBody")
public String getTransactionBody() {
return myTransactionBody;
}
public void setTransactionBody(String theTransactionBody) {
myTransactionBody = theTransactionBody;
}
}

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>
@ -21,12 +22,5 @@
</bean> </bean>
<bean id="fhirContext" class="ca.uhn.fhir.context.FhirContext"> <bean id="fhirContext" class="ca.uhn.fhir.context.FhirContext">
<property name="restfulClientFactory" ref="restfulClientFactory"/>
</bean>
<bean id="restfulClientFactory" class="ca.uhn.fhir.rest.client.RestfulClientFactory">
<property name="fhirContext" ref="fhirContext"/>
<property name="connectTimeout" value="4000"/>
<property name="socketTimeout" value="10000"/>
</bean> </bean>
</beans> </beans>

View File

@ -31,22 +31,22 @@
<col class="col-xs-7" /> <col class="col-xs-7" />
</colgroup> </colgroup>
<tbody> <tbody>
<tr th:if="${conf.implementation.description} != null and #{!string.isEmpty(conf.implementation.description)}"> <tr th:if="#{!strings.isEmpty(conf.implementation.description.value)}">
<td>Server</td> <td>Server</td>
<td th:utext="'' + ${conf.implementation.description}">HAPI Restful Server</td> <td th:utext="'' + ${conf.implementation.description}">HAPI Restful Server</td>
</tr> </tr>
<tr th:if="#{!strings.isEmpty(conf.software.name.value)} or #{!strings.isEmpty(conf.software.version.value)}">
<td>Software</td>
<td>
<th:block th:utext="'' + ${conf.software.name}"/> - <th:block th:utext="'' + ${conf.software.version}"/>
</td>
</tr>
<tr> <tr>
<td>FHIR Base</td> <td>FHIR Base</td>
<td> <td>
<a th:href="${base}" th:text="${base}"></a> <a th:href="${base}" th:text="${base}"></a>
</td> </td>
</tr> </tr>
<tr th:if="#{string.isEmpty(conf.software.name)} == false and #{string.isEmpty(conf.software.version)} == false">
<td>Software</td>
<td>
<th:block th:utext="'' + ${conf.software.name}"/> - <th:block th:utext="'' + ${conf.software.version}"/>
</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -67,7 +67,7 @@
Retrieve the server's <b>conformance</b> statement. Retrieve the server's <b>conformance</b> statement.
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
<div class="col-sm-2"> <div class="col-sm-2 form-group">
<button type="button" id="fetch-conformance-btn" <button type="button" id="fetch-conformance-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block"> data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<i class="fa fa-dot-circle-o"></i> <i class="fa fa-dot-circle-o"></i>
@ -144,6 +144,61 @@
</div> </div>
<!-- Transaction -->
<br clear="all"/>
<div class="row-fluid">
Post a bundle containing multiple resources to the server and
store all resources within a single atomic transaction.
</div>
<div class="row-fluid">
<div class="col-sm-2">
<button type="button" id="transaction-btn"
data-loading-text="Processing &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<i class="fa fa-files-o"></i>
Transaction
</button>
</div>
<div class='col-sm-10'>
<div class="form-group">
<div class='input-group'>
<div class="input-group-addon">
Bundle
<span class="loadingStar">*</span>
</div>
<textarea class="form-control" id="transaction-body" style="white-space: nowrap; overflow: auto;" placeholder="(place transaction bundle body here)" rows="1">
<th:block th:if="${transactionBundle} != null" th:text="${transactionBundle}"/>
</textarea>
</div>
</div>
</div>
<script type="text/javascript">
var textAreaChanger = function() {
createBodyOriginalHeight = $('#transaction-body').height();
$('#transaction-body').animate({height: "200px"}, 500);
}
$('#transaction-body').focus(textAreaChanger);
$('#transaction-btn').click(
function() {
var btn = $(this);
btn.button('loading');
var id = $('#transaction-id').val();
if (id != null) btn.append($('<input />', { type: 'hidden', name: 'resource-create-id', value: id }));
var body = $('#transaction-body').val();
btn.append($('<input />', { type: 'hidden', name: 'transactionBody', value: body }));
$("#outerForm").attr("method", "post");
$("#outerForm").attr("action", "create").submit();
});
$( document ).ready(function() {
/* if ($('#resource-create-id').val() != "") {
buttonChanger();
textAreaChanger();
$('#transaction-body').focus();
}
*/ });
</script>
</div>
<!-- Get Tags --> <!-- Get Tags -->
<br clear="all"/> <br clear="all"/>
@ -151,7 +206,7 @@
Show all of the tags currently in use on the server Show all of the tags currently in use on the server
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
<div class="col-sm-2"> <div class="col-sm-2 form-group">
<button type="button" id="get-server-tags-btn" <button type="button" id="get-server-tags-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block"> data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<i class="fa fa-tags"></i> <i class="fa fa-tags"></i>

View File

@ -1,8 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<div th:fragment="banner" class="well"> <div th:fragment="banner" class="well">
<div> <div>
This is a RESTful server tester, which can be used to send This is a RESTful server tester, which can be used to send
requests to, and receive responses from the server. requests to, and receive responses from the server.
</div>
</div> </div>
</html> </html>

View File

@ -59,7 +59,7 @@
<ul class="nav nav-sidebar"> <ul class="nav nav-sidebar">
<li th:class="${resourceName.empty} ? 'active' : ''"> <li th:class="${resourceName.empty} ? 'active' : ''">
<a href="#" onclick="doAction(this, 'home', null);">Server Home</a> <a href="#" onclick="doAction(this, 'home', null);">Server Home/Actions</a>
</li> </li>
</ul> </ul>

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' )

View File

@ -102,6 +102,35 @@
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-tinder-plugin</artifactId>
<versionRange>[0.4-SNAPSHOT,)</versionRange>
<goals>
<goal>generate-structures</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build> </build>
</project> </project>