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) {
return theO1.getSimpleName().compareTo(theO2.getSimpleName());
} else if (o1res) {
return 1;
}else {
return -1;
} else if (o1res == false && o2res == false) {
return 0;
}else {
return 1;
}
}});

View File

@ -221,7 +221,12 @@ public class Bundle extends BaseBundle /* implements IElement */{
RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
if (theResource.getId() != null && StringUtils.isNotBlank(theResource.getId().getValue())) {
entry.getTitle().setValue(def.getName() + " " + theResource.getId().getValue());
String title = ResourceMetadataKeyEnum.TITLE.get(theResource);
if (title != null) {
entry.getTitle().setValue(title);
} else {
entry.getTitle().setValue(def.getName() + " " + theResource.getId().getValue());
}
StringBuilder b = new StringBuilder();
b.append(theServerBase);

View File

@ -25,6 +25,8 @@ import static org.apache.commons.lang3.StringUtils.*;
import java.util.Date;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -71,7 +73,7 @@ public abstract class ResourceMetadataKeyEnum<T> {
theResource.getResourceMetadata().put(PREVIOUS_ID, theObject);
}
};
/**
* 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
@ -128,6 +130,26 @@ public abstract class ResourceMetadataKeyEnum<T> {
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
public boolean equals(Object obj) {
if (this == obj)
@ -200,6 +221,8 @@ public abstract class ResourceMetadataKeyEnum<T> {
return true;
}
public abstract T get(IResource theResource);
@Override
@ -210,6 +233,10 @@ public abstract class ResourceMetadataKeyEnum<T> {
return result;
}
private String name() {
return myValue;
}
public abstract void put(IResource theResource, T theObject);
@Override
@ -217,32 +244,28 @@ public abstract class ResourceMetadataKeyEnum<T> {
return myValue;
}
private String name() {
return myValue;
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 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) {
Object retValObj = theResourceMetadata.get(theKey);
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());
}
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
public String getValueAsQueryToken() {
if (org.apache.commons.lang3.StringUtils.isNotBlank(getSystem().getValueAsString())) {
return getSystem().getValueAsString() + '|' + getCode().getValueAsString();
} else {
return getSystem().getValueAsString() + '|' + getCode().getValueAsString();
} else if (getSystem().getValue()==null) {
return getCode().getValueAsString();
} else {
return '|' + getCode().getValueAsString();
}
}

View File

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

View File

@ -69,17 +69,100 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
private boolean myIgnoreMissingTemplates = true;
private TemplateEngine myProfileTemplateEngine;
private HashMap<String, String> myProfileToNarrativeName;
private HashMap<Class<?>, String> myClassToNarrativeName;
private TemplateEngine myTitleTemplateEngine;
private HashMap<String, String> myProfileToName;
private HashMap<Class<?>, String> myClassToName;
private HashMap<String, String> myNameToNarrativeTemplate;
private boolean myApplyDefaultDatatypeTemplates=true;
private boolean myApplyDefaultDatatypeTemplates = true;
private volatile boolean myInitialized;
private HashMap<String, String> myNameToTitleTemplate;
@Override
public NarrativeDt generateNarrative(IResource theResource) {
return generateNarrative(null, theResource);
}
@Override
public String generateTitle(IResource theResource) {
return generateTitle(null, theResource);
}
@Override
public String generateTitle(String theProfile, IResource theResource) {
if (!myInitialized) {
initialize();
}
String name = null;
if (StringUtils.isNotBlank(theProfile)) {
name = myProfileToName.get(theProfile);
}
if (name == null) {
name = myClassToName.get(theResource.getClass());
}
if (name == null) {
if (myIgnoreMissingTemplates) {
ourLog.debug("No title template available for profile: {}", theProfile);
return null;
} else {
throw new DataFormatException("No title template for class " + theResource.getClass().getCanonicalName());
}
}
try {
Context context = new Context();
context.setVariable("resource", theResource);
String result = myTitleTemplateEngine.process(name, context);
StringBuilder b = new StringBuilder();
boolean inTag = false;
for (int i = 0; i < result.length(); i++) {
char nextChar = result.charAt(i);
char prevChar = i > 0 ? result.charAt(i - 1) : '\n';
if (nextChar == '<') {
inTag = true;
continue;
} else if (inTag) {
if (nextChar == '>') {
inTag = false;
}
continue;
} else if (nextChar <= ' ') {
if (prevChar <= ' ' || prevChar == '>') {
continue;
} else {
b.append(' ');
}
} else {
b.append(nextChar);
}
}
while (b.length() > 0 && b.charAt(b.length()-1) == ' ') {
b.setLength(b.length() - 1);
}
result = b.toString();
if (result.startsWith("<") && result.contains(">")) {
result = result.substring(result.indexOf('>') + 1);
}
if (result.endsWith(">") && result.contains("<")) {
result = result.substring(0, result.lastIndexOf('<'));
}
return result;
} catch (Exception e) {
if (myIgnoreFailures) {
ourLog.error("Failed to generate narrative", e);
return "No title available - Error: " + e.getMessage();
} else {
throw new DataFormatException(e);
}
}
}
@Override
public NarrativeDt generateNarrative(String theProfile, IResource theResource) {
if (!myInitialized) {
@ -88,10 +171,10 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String name = null;
if (StringUtils.isNotBlank(theProfile)) {
name = myProfileToNarrativeName.get(theProfile);
name = myProfileToName.get(theProfile);
}
if (name == null) {
name = myClassToNarrativeName.get(theResource.getClass());
name = myClassToName.get(theResource.getClass());
}
if (name == null) {
@ -106,7 +189,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
try {
Context context = new Context();
context.setVariable("resource", theResource);
String result = myProfileTemplateEngine.process(name, context);
if (myCleanWhitespace) {
@ -131,9 +214,10 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
if (myInitialized) {
return;
}
myProfileToNarrativeName = new HashMap<String, String>();
myClassToNarrativeName = new HashMap<Class<?>, String>();
myProfileToName = new HashMap<String, String>();
myClassToName = new HashMap<Class<?>, String>();
myNameToNarrativeTemplate = new HashMap<String, String>();
myNameToTitleTemplate = new HashMap<String, String>();
List<String> propFileName = getPropertyFile();
@ -160,6 +244,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
myProfileTemplateEngine.setDialect(dialect);
myProfileTemplateEngine.initialize();
}
{
myTitleTemplateEngine = new TemplateEngine();
TemplateResolver resolver = new TemplateResolver();
resolver.setResourceResolver(new TitleResourceResolver());
myTitleTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect();
myTitleTemplateEngine.setDialect(dialect);
myTitleTemplateEngine.initialize();
}
myInitialized = true;
}
@ -167,12 +260,9 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
protected abstract List<String> getPropertyFile();
/**
* If set to <code>true</code> (which is the default), most whitespace will
* be trimmed from the generated narrative before it is returned.
* If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative before it is returned.
* <p>
* Note that in order to preserve formatting, not all whitespace is trimmed.
* Repeated whitespace characters (e.g. "\n \n ") will be
* trimmed to a single space.
* Note that in order to preserve formatting, not all whitespace is trimmed. Repeated whitespace characters (e.g. "\n \n ") will be trimmed to a single space.
* </p>
*/
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
* during narrative generation the generator will suppress any generated
* exceptions, and simply return a default narrative indicating that no
* narrative is available.
* If set to <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
* indicating that no narrative is available.
*/
public boolean isIgnoreFailures() {
return myIgnoreFailures;
}
/**
* If set to true, will return an empty narrative block for any profiles
* where no template is available
* If set to true, will return an empty narrative block for any profiles where no template is available
*/
public boolean isIgnoreMissingTemplates() {
return myIgnoreMissingTemplates;
}
/**
* If set to <code>true</code> (which is the default), most whitespace will
* be trimmed from the generated narrative before it is returned.
* If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative before it is returned.
* <p>
* Note that in order to preserve formatting, not all whitespace is trimmed.
* Repeated whitespace characters (e.g. "\n \n ") will be
* trimmed to a single space.
* Note that in order to preserve formatting, not all whitespace is trimmed. Repeated whitespace characters (e.g. "\n \n ") will be trimmed to a single space.
* </p>
*/
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
* during narrative generation the generator will suppress any generated
* exceptions, and simply return a default narrative indicating that no
* narrative is available.
* If set to <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
* indicating that no narrative is available.
*/
public void setIgnoreFailures(boolean theIgnoreFailures) {
myIgnoreFailures = theIgnoreFailures;
}
/**
* If set to true, will return an empty narrative block for any profiles
* where no template is available
* If set to true, will return an empty narrative block for any profiles where no template is available
*/
public void setIgnoreMissingTemplates(boolean theIgnoreMissingTemplates) {
myIgnoreMissingTemplates = theIgnoreMissingTemplates;
@ -230,7 +311,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
private void loadProperties(String propFileName) throws IOException {
ourLog.debug("Loading narrative properties file: {}", propFileName);
Properties file = new Properties();
InputStream resource = loadResource(propFileName);
@ -245,13 +326,22 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String narrativePropName = name + ".narrative";
String narrativeName = file.getProperty(narrativePropName);
if (isBlank(narrativeName)) {
throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' in file " + propFileName);
String titlePropName = name + ".title";
String titleName = file.getProperty(titlePropName);
if (isBlank(narrativeName) && isBlank(titleName)) {
throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' or '" + titlePropName + "' in file " + propFileName);
}
String narrative = IOUtils.toString(loadResource(narrativeName));
myProfileToNarrativeName.put(file.getProperty(nextKey), name);
myNameToNarrativeTemplate.put(name, narrative);
myProfileToName.put(file.getProperty(nextKey), name);
if (StringUtils.isNotBlank(narrativeName)) {
String narrative = IOUtils.toString(loadResource(narrativeName));
myNameToNarrativeTemplate.put(name, narrative);
}
if (StringUtils.isNotBlank(titleName)) {
String title = IOUtils.toString(loadResource(titleName));
myNameToTitleTemplate.put(name, title);
}
} else if (nextKey.endsWith(".class")) {
@ -262,9 +352,9 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String className = file.getProperty(nextKey);
Class<?> dtClass;
Class<?> clazz;
try {
dtClass = Class.forName(className);
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
ourLog.warn("Unknown datatype class '{}' identified in narrative file {}", name, propFileName);
continue;
@ -272,16 +362,27 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String narrativePropName = name + ".narrative";
String narrativeName = file.getProperty(narrativePropName);
if (isBlank(narrativeName)) {
throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' in file " + propFileName);
String titlePropName = name + ".title";
String titleName = file.getProperty(titlePropName);
if (isBlank(narrativeName) && isBlank(titleName)) {
throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' or '" + titlePropName + "' in file " + propFileName);
}
String narrative = IOUtils.toString(loadResource(narrativeName));
myClassToNarrativeName.put(dtClass, name);
myNameToNarrativeTemplate.put(name, narrative);
myClassToName.put(clazz, name);
if (StringUtils.isNotBlank(narrativeName)) {
String narrative = IOUtils.toString(loadResource(narrativeName));
myNameToNarrativeTemplate.put(name, narrative);
}
if (StringUtils.isNotBlank(titleName)) {
String title = IOUtils.toString(loadResource(titleName));
myNameToTitleTemplate.put(name, title);
}
} else if (nextKey.endsWith(".narrative")) {
continue;
} else if (nextKey.endsWith(".title")) {
continue;
} else {
throw new ConfigurationException("Invalid property name: " + nextKey);
}
@ -379,14 +480,13 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue);
final Object value = expression.execute(configuration, theArguments);
theElement.removeAttribute(theAttributeName);
theElement.clearChildren();
Context context = new Context();
context.setVariable("resource", value);
String name = myClassToNarrativeName.get(value.getClass());
String name = myClassToName.get(value.getClass());
if (name == null) {
if (myIgnoreMissingTemplates) {
ourLog.debug("No narrative template available for type: {}", value.getClass());
@ -438,4 +538,21 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
return new ReaderInputStream(new StringReader(template));
}
}
private final class TitleResourceResolver implements IResourceResolver {
@Override
public String getName() {
return getClass().getCanonicalName();
}
@Override
public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theName) {
String template = myNameToTitleTemplate.get(theName);
if (template == null) {
ourLog.info("No narative template for resource profile: {}", theName);
return new ReaderInputStream(new StringReader(""));
}
return new ReaderInputStream(new StringReader(template));
}
}
}

View File

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

View File

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

View File

@ -31,7 +31,7 @@ import ca.uhn.fhir.rest.method.QualifiedParamList;
public class IdentifierListParam implements IQueryParameterOr {
private List<IdentifierDt> myIdentifiers = new ArrayList<IdentifierDt>();
/**
* 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;
} else if (ReferenceParam.class.isAssignableFrom(type)) {
myParamType = SearchParamTypeEnum.REFERENCE;
} else if (IdentifierListParam.class.isAssignableFrom(type)) {
myParamType = SearchParamTypeEnum.TOKEN;
} else {
throw new ConfigurationException("Unknown search parameter type: " + type);
}

View File

@ -20,11 +20,10 @@ package ca.uhn.fhir.rest.server;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Method;
@ -46,8 +45,9 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
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.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import ca.uhn.fhir.context.FhirContext;
@ -542,22 +542,26 @@ public class RestfulServer extends HttpServlet {
theResponse.setContentType("text/plain");
theResponse.setCharacterEncoding("UTF-8");
theResponse.getWriter().write(e.getMessage());
} 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();
Issue issue = oo.addIssue();
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);
@ -776,6 +780,14 @@ public class RestfulServer extends HttpServlet {
bundle.getLinkSelf().setValue(theCompleteUrl);
for (IResource next : theResult) {
if (theContext.getNarrativeGenerator() != null) {
String title = theContext.getNarrativeGenerator().generateTitle(next);
if (StringUtils.isNotBlank(title)) {
ResourceMetadataKeyEnum.TITLE.put(next, title);
}
}
bundle.addResource(next, theContext, theServerBase);
}

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.narrative=classpath:ca/uhn/fhir/narrative/Patient.html
patient.title=classpath:ca/uhn/fhir/narrative/title/Patient.html
diagnosticreport.class=ca.uhn.fhir.model.dstu.resource.DiagnosticReport
diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html
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());
}
@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);
String output = gen.generateNarrative("http://hl7.org/fhir/profiles/Patient", value).getDiv().getValueAsString();
ourLog.info(output);
String output = gen.generateNarrative(value).getDiv().getValueAsString();
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> joe john <b>BLOW </b></div>"));
String title = gen.generateTitle(value);
assertEquals("joe john BLOW (123456)", title);
ourLog.info(title);
}
@Test
@ -105,6 +108,11 @@ public class DefaultThymeleafNarrativeGeneratorTest {
ourLog.info(output);
assertThat(output, StringContains.containsString("<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
FhirContext context = new FhirContext();

View File

@ -43,28 +43,44 @@ public class ExceptionTest {
private static RestfulServer servlet;
@Test
public void testSearchNormalMatch() throws Exception {
public void testInternalError() throws Exception {
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?throwInternalError=aaa");
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().newXmlParser().parseResource(responseContent);
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);
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);
OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newXmlParser().parseResource(responseContent);
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

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

View File

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

View File

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

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) {
if (theParam == null) {
return;

View File

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

View File

@ -8,7 +8,10 @@ import org.apache.commons.lang3.StringUtils;
@Entity
@Table(name = "HFJ_SPIDX_TOKEN" /* , indexes = { @Index(name = "IDX_SP_TOKEN", columnList = "SP_SYSTEM,SP_VALUE") } */)
@org.hibernate.annotations.Table(appliesTo = "HFJ_SPIDX_TOKEN", indexes = { @org.hibernate.annotations.Index(name = "IDX_SP_TOKEN", columnNames = { "RES_TYPE", "SP_NAME", "SP_SYSTEM", "SP_VALUE" }) })
@org.hibernate.annotations.Table(appliesTo = "HFJ_SPIDX_TOKEN", indexes = {
@org.hibernate.annotations.Index(name = "IDX_SP_TOKEN", columnNames = { "RES_TYPE", "SP_NAME", "SP_SYSTEM", "SP_VALUE" }),
@org.hibernate.annotations.Index(name = "IDX_SP_TOKEN_UNQUAL", columnNames = { "RES_TYPE", "SP_NAME", "SP_VALUE" })
})
public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam {
private static final long serialVersionUID = 1L;

View File

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

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.RestResource;
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.provider.ServerConformanceProvider;
public class JpaConformanceProvider extends ServerConformanceProvider {
private String myImplementationDescription;
private IFhirSystemDao mySystemDao;
public JpaConformanceProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao) {
@ -19,24 +21,29 @@ public class JpaConformanceProvider extends ServerConformanceProvider {
mySystemDao = theSystemDao;
super.setCache(false);
}
@Override
public Conformance getServerConformance() {
Map<String, Long> counts = mySystemDao.getResourceCounts();
Conformance retVal = super.getServerConformance();
for (Rest nextRest : retVal.getRest()) {
for (RestResource nextResource : nextRest.getResource()) {
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));
}
}
}
retVal.getImplementation().setDescription(myImplementationDescription);
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.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.gclient.IQuery;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.IdentifierListParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
@ -122,6 +124,50 @@ public class FhirResourceDaoTest {
assertEquals(o1id.toUnqualifiedVersionless(), p1.getManagingOrganization().getReference().toUnqualifiedVersionless());
}
@Test
public void testSearchTokenParam() {
Patient patient = new Patient();
patient.addIdentifier("urn:system", "testSearchTokenParam001");
patient.addName().addFamily("Tester").addGiven("testSearchTokenParam1");
ourPatientDao.create(patient);
patient = new Patient();
patient.addIdentifier("urn:system", "testSearchTokenParam002");
patient.addName().addFamily("Tester").addGiven("testSearchTokenParam2");
ourPatientDao.create(patient);
{
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testSearchTokenParam001"));
IBundleProvider retrieved = ourPatientDao.search(map);
assertEquals(1, retrieved.size());
}
{
SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_IDENTIFIER, new IdentifierDt(null, "testSearchTokenParam001"));
IBundleProvider retrieved = ourPatientDao.search(map);
assertEquals(1, retrieved.size());
}
{
SearchParameterMap map = new SearchParameterMap();
IdentifierListParam listParam = new IdentifierListParam();
listParam.addIdentifier(new IdentifierDt("urn:system", "testSearchTokenParam001"));
listParam.addIdentifier(new IdentifierDt("urn:system", "testSearchTokenParam002"));
map.add(Patient.SP_IDENTIFIER, listParam);
IBundleProvider retrieved = ourPatientDao.search(map);
assertEquals(2, retrieved.size());
}
{
SearchParameterMap map = new SearchParameterMap();
IdentifierListParam listParam = new IdentifierListParam();
listParam.addIdentifier(new IdentifierDt(null, "testSearchTokenParam001"));
listParam.addIdentifier(new IdentifierDt("urn:system", "testSearchTokenParam002"));
map.add(Patient.SP_IDENTIFIER, listParam);
IBundleProvider retrieved = ourPatientDao.search(map);
assertEquals(2, retrieved.size());
}
}
@Test
public void testIdParam() {
Patient patient = new Patient();
@ -253,13 +299,12 @@ public class FhirResourceDaoTest {
@Test
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());
}
/*
* 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
*/
value = ourDeviceDao.search(new SearchParameterMap());
assertEquals(0, value.size());
List<IResource> res = value.getResources(0, 0);
@ -361,6 +406,7 @@ public class FhirResourceDaoTest {
Patient patient = new Patient();
patient.addIdentifier("urn:system", "001");
patient.addName().addFamily("testSearchNameParam01Fam").addGiven("testSearchNameParam01Giv");
ResourceMetadataKeyEnum.TITLE.put(patient, "P1TITLE");
id1 = ourPatientDao.create(patient).getId();
}
{
@ -375,6 +421,7 @@ public class FhirResourceDaoTest {
List<Patient> patients = toList(ourPatientDao.search(params));
assertEquals(1, patients.size());
assertEquals(id1.getIdPart(), patients.get(0).getId().getIdPart());
assertEquals("P1TITLE", ResourceMetadataKeyEnum.TITLE.get(patients.get(0)));
// Given name shouldn't return for family param
params = new HashMap<String, IQueryParameterType>();

View File

@ -148,6 +148,31 @@
<target>1.7</target>
</configuration>
</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>
</pluginManagement>
<plugins>

View File

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

View File

@ -31,6 +31,7 @@
<!-- <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="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="password" value="SA"/>
</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>
<html lang="en">
<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>
This is the home for the FHIR test server operated by
<a href="http://uhn.ca">University Health Network</a>.
</p>
<p>
<b style="color: red;">This is not a production server!</b>
<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 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.
</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-name>fhirServlet</servlet-name>
<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>
</servlet>

View File

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

View File

@ -23,7 +23,7 @@ public class UhnFhirTestApp {
public static void main(String[] args) throws Exception {
// new File("target/testdb").mkdirs();
System.setProperty("fhir.db.location", "target/testdb");
System.setProperty("fhir.db.location", "/target/testdb");
int myPort = 8888;
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="/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/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">
<dependency-type>uses</dependency-type>
</dependent-module>

View File

@ -31,10 +31,10 @@
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</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.to.model.HomeRequest;
import ca.uhn.fhir.to.model.ResourceRequest;
import ca.uhn.fhir.to.model.TransactionRequest;
@org.springframework.stereotype.Controller()
public class Controller {
@ -67,6 +68,39 @@ public class Controller {
@Autowired
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" })
public String actionConformance(final HomeRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) {
addCommonParams(theRequest, theModel);
@ -427,18 +461,7 @@ public class Controller {
return;
}
body = body.trim();
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();
body = preProcessMessageBody(body);
IResource resource;
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) {
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">
<property name="servers">
<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>hi , Health Intersections , http://fhir.healthintersections.com.au/open</value>
<value>furore , Spark - Furore Reference Server , http://spark.furore.com/fhir</value>
@ -21,12 +22,5 @@
</bean>
<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>
</beans>

View File

@ -31,22 +31,22 @@
<col class="col-xs-7" />
</colgroup>
<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 th:utext="'' + ${conf.implementation.description}">HAPI Restful Server</td>
</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>
<td>FHIR Base</td>
<td>
<a th:href="${base}" th:text="${base}"></a>
</td>
</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>
</table>
@ -67,7 +67,7 @@
Retrieve the server's <b>conformance</b> statement.
</div>
<div class="row-fluid">
<div class="col-sm-2">
<div class="col-sm-2 form-group">
<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">
<i class="fa fa-dot-circle-o"></i>
@ -144,6 +144,61 @@
</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 -->
<br clear="all"/>
@ -151,7 +206,7 @@
Show all of the tags currently in use on the server
</div>
<div class="row-fluid">
<div class="col-sm-2">
<div class="col-sm-2 form-group">
<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">
<i class="fa fa-tags"></i>

View File

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

View File

@ -59,7 +59,7 @@
<ul class="nav nav-sidebar">
<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>
</ul>

View File

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

View File

@ -102,6 +102,35 @@
</configuration>
</plugin>
</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>
</project>