Correct issues uncovered during connectathon

This commit is contained in:
James Agnew 2016-01-11 07:42:13 -05:00
parent 552842e547
commit 589059256f
69 changed files with 8945 additions and 896 deletions

View File

@ -52,6 +52,7 @@ import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.VersionUtil;
import ca.uhn.fhir.validation.FhirValidator;
/**
@ -113,6 +114,8 @@ public class FhirContext {
}
private FhirContext(FhirVersionEnum theVersion, Collection<Class<? extends IBaseResource>> theResourceTypes) {
VersionUtil.getVersion();
if (theVersion != null) {
if (!theVersion.isPresentOnClasspath()) {
throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructuresForSpecifiedVersion", theVersion.name()));
@ -465,9 +468,6 @@ public class FhirContext {
}
public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) {
if (theNarrativeGenerator != null) {
theNarrativeGenerator.setFhirContext(this);
}
myNarrativeGenerator = theNarrativeGenerator;
}
@ -509,6 +509,8 @@ public class FhirContext {
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_1 DSTU 2.1}
*
* @since 1.4
*/
public static FhirContext forDstu2_1() {
return new FhirContext(FhirVersionEnum.DSTU2_1);

View File

@ -30,8 +30,10 @@ import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
@ -108,6 +110,7 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
} else {
nextDef = theClassToElementDefinitions.get(next);
BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef;
/*
* In HAPI 1.3 the following applied:
* Elements which are called foo[x] and have a choice which is a profiled datatype must use the
@ -115,19 +118,19 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
* element fooString when encoded, because markdown is a profile of string. This is according to the
* FHIR spec
*
* As of HAPI 1.4 this has been disabled after conversation with Grahame. It appears
* that it is not correct behaviour.
* Note that as of HAPI 1.4 this applies only to non-primitive datatypes after discussion
* with Grahame.
*/
// if (nextDef instanceof IRuntimeDatatypeDefinition) {
// IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef;
// if (nextDefDatatype.getProfileOf() != null) {
// nextDefForChoice = null;
// nonPreferred = true;
// Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf();
// BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType);
// elementName = getElementName() + StringUtils.capitalize(elementDef.getName());
// }
// }
if (nextDef instanceof IRuntimeDatatypeDefinition) {
IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef;
if (nextDefDatatype.getProfileOf() != null && !IPrimitiveType.class.isAssignableFrom(next)) {
nextDefForChoice = null;
nonPreferred = true;
Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf();
BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType);
elementName = getElementName() + StringUtils.capitalize(elementDef.getName());
}
}
if (nextDefForChoice != null) {
elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName());
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.model.base.composite;
import org.hl7.fhir.instance.model.api.INarrative;
import ca.uhn.fhir.model.api.BaseIdentifiableElement;
import ca.uhn.fhir.model.api.ICompositeDatatype;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
@ -28,10 +30,31 @@ import ca.uhn.fhir.model.primitive.XhtmlDt;
/**
* @param <T> The narrative status enum type
*/
public abstract class BaseNarrativeDt<T extends Enum<?>> extends BaseIdentifiableElement implements ICompositeDatatype {
public abstract class BaseNarrativeDt<T extends Enum<?>> extends BaseIdentifiableElement implements ICompositeDatatype, INarrative {
public abstract BoundCodeDt<T> getStatus();
@Override
public void setDivAsString(String theString) {
getDiv().setValueAsString(theString);
}
@Override
public String getDivAsString() {
return getDiv().getValueAsString();
}
@Override
public INarrative setStatusAsString(String theString) {
getStatus().setValueAsString(theString);
return this;
}
@Override
public String getStatusAsString() {
return getStatus().getValueAsString();
}
public abstract XhtmlDt getDiv();
}

View File

@ -35,7 +35,9 @@ import java.util.Properties;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.INarrative;
import org.thymeleaf.Arguments;
import org.thymeleaf.Configuration;
import org.thymeleaf.TemplateEngine;
@ -62,7 +64,7 @@ import org.thymeleaf.templateresolver.TemplateResolver;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.parser.DataFormatException;
public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGenerator {
@ -77,10 +79,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
private boolean myIgnoreMissingTemplates = true;
private volatile boolean myInitialized;
private HashMap<String, String> myNameToNarrativeTemplate;
private HashMap<String, String> myNameToTitleTemplate;
private TemplateEngine myProfileTemplateEngine;
private HashMap<String, String> myProfileToName;
private TemplateEngine myTitleTemplateEngine;
public BaseThymeleafNarrativeGenerator() {
myThymeleafConfig = new Configuration();
@ -90,44 +89,31 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
myThymeleafConfig.initialize();
}
@Override
public void generateNarrative(IBaseResource theResource, BaseNarrativeDt<?> theNarrative) {
generateNarrative(null, theResource, theNarrative);
}
@Override
public void setFhirContext(FhirContext theFhirContext) {
if (theFhirContext == null) {
throw new NullPointerException("Can not set theFhirContext to null");
}
if (myFhirContext != null && myFhirContext != theFhirContext) {
throw new IllegalStateException("Narrative generators may not be reused/shared across multiple FhirContext instances");
}
myFhirContext = theFhirContext;
}
@Override
public void generateNarrative(String theProfile, IBaseResource theResource, BaseNarrativeDt<?> theNarrative) {
public void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative) {
if (!myInitialized) {
initialize();
initialize(theContext);
}
String name = null;
if (StringUtils.isNotBlank(theProfile)) {
name = myProfileToName.get(theProfile);
}
if (name == null) {
name = myClassToName.get(theResource.getClass());
}
if (name == null) {
name = myFhirContext.getResourceDefinition(theResource).getName().toLowerCase();
name = theContext.getResourceDefinition(theResource).getName().toLowerCase();
}
if (name == null) {
if (myIgnoreMissingTemplates) {
ourLog.debug("No narrative template available for profile: {}", theProfile);
theNarrative.getDiv().setValueAsString("<div>No narrative template available for resource profile: " + theProfile + "</div>");
theNarrative.getStatus().setValueAsString("empty");
ourLog.debug("No narrative template available for resorce: {}", name);
try {
theNarrative.setDivAsString("<div>No narrative template available for resource : " + name + "</div>");
} catch (Exception e) {
// last resort..
}
theNarrative.setStatusAsString("empty");
return;
} else {
throw new DataFormatException("No narrative template for class " + theResource.getClass().getCanonicalName());
@ -137,7 +123,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
try {
Context context = new Context();
context.setVariable("resource", theResource);
context.setVariable("fhirVersion", myFhirContext.getVersion().getVersion().name());
context.setVariable("fhirVersion", theContext.getVersion().getVersion().name());
String result = myProfileTemplateEngine.process(name, context);
@ -151,14 +137,18 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
return;
}
theNarrative.getDiv().setValueAsString(result);
theNarrative.getStatus().setValueAsString("generated");
theNarrative.setDivAsString(result);
theNarrative.setStatusAsString("generated");
return;
} catch (Exception e) {
if (myIgnoreFailures) {
ourLog.error("Failed to generate narrative", e);
theNarrative.getDiv().setValueAsString("<div>No narrative available - Error: " + e.getMessage() + "</div>");
theNarrative.getStatus().setValueAsString("empty");
try {
theNarrative.setDivAsString("<div>No narrative available - Error: " + e.getMessage() + "</div>");
} catch (Exception e1) {
// last resort..
}
theNarrative.setStatusAsString("empty");
return;
} else {
throw new DataFormatException(e);
@ -166,98 +156,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
}
@Override
public String generateTitle(IBaseResource theResource) {
return generateTitle( null, theResource);
}
@Override
public String generateTitle(String theProfile, IBaseResource theResource) {
if (!myInitialized) {
initialize();
}
ourLog.trace("Generating resource title {}", theResource);
String name = null;
if (StringUtils.isNotBlank(theProfile)) {
name = myProfileToName.get(theProfile);
}
if (name == null) {
name = myClassToName.get(theResource.getClass());
}
if (name == null) {
name = myFhirContext.getResourceDefinition(theResource).getName().toLowerCase();
}
ourLog.trace("Template name is {}", name);
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);
context.setVariable("fhirVersion", myFhirContext.getVersion().getVersion().name());
String result = myTitleTemplateEngine.process(name, context);
ourLog.trace("Produced {}", result);
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('<'));
}
result = result.replace("&gt;", ">").replace("&lt;", "<").replace("&amp;", "&");
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);
}
}
}
protected abstract List<String> getPropertyFile();
@ -270,17 +169,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
}
private synchronized void initialize() {
private synchronized void initialize(FhirContext theContext) {
if (myInitialized) {
return;
}
ourLog.info("Initializing narrative generator");
myProfileToName = new HashMap<String, String>();
myClassToName = new HashMap<Class<?>, String>();
myNameToNarrativeTemplate = new HashMap<String, String>();
myNameToTitleTemplate = new HashMap<String, String>();
List<String> propFileName = getPropertyFile();
@ -303,23 +200,11 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
myProfileTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect();
HashSet<IProcessor> additionalProcessors = new HashSet<IProcessor>();
additionalProcessors.add(new NarrativeAttributeProcessor());
additionalProcessors.add(new NarrativeAttributeProcessor(theContext));
dialect.setAdditionalProcessors(additionalProcessors);
myProfileTemplateEngine.setDialect(dialect);
myProfileTemplateEngine.initialize();
}
{
myTitleTemplateEngine = new TemplateEngine();
TemplateResolver resolver = new TemplateResolver();
resolver.setResourceResolver(new TitleResourceResolver());
myTitleTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect();
HashSet<IProcessor> additionalProcessors = new HashSet<IProcessor>();
additionalProcessors.add(new NarrativeAttributeProcessor());
dialect.setAdditionalProcessors(additionalProcessors);
myTitleTemplateEngine.setDialect(dialect);
myTitleTemplateEngine.initialize();
}
myInitialized = true;
}
@ -369,22 +254,14 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String narrativePropName = name + ".narrative";
String narrativeName = file.getProperty(narrativePropName);
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);
if (isBlank(narrativeName)) {
throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' in file " + propFileName);
}
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")) {
@ -403,10 +280,6 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
clazz = null;
}
// if (isBlank(narrativeName) && isBlank(titleName)) {
// throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' or '" + titlePropName + "' in file " + propFileName);
// }
if (clazz != null) {
myClassToName.put(clazz, name);
}
@ -424,18 +297,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
continue;
} else if (nextKey.endsWith(".title")) {
String name = nextKey.substring(0, nextKey.indexOf(".title"));
if (isBlank(name)) {
continue;
}
String titlePropName = name + ".title";
String titleName = file.getProperty(titlePropName);
if (StringUtils.isNotBlank(titleName)) {
String title = IOUtils.toString(loadResource(titleName));
myNameToTitleTemplate.put(name, title);
}
continue;
ourLog.debug("Ignoring title property as narrative generator no longer generates titles: {}", nextKey);
} else {
throw new ConfigurationException("Invalid property name: " + nextKey);
}
@ -555,13 +417,13 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
return b.toString();
}
private FhirContext myFhirContext;
public class NarrativeAttributeProcessor extends AbstractAttrProcessor {
private FhirContext myContext;
protected NarrativeAttributeProcessor() {
protected NarrativeAttributeProcessor(FhirContext theContext) {
super("narrative");
myContext = theContext;
}
@Override
@ -569,6 +431,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
return 0;
}
@SuppressWarnings("unchecked")
@Override
protected ProcessorResult processAttribute(Arguments theArguments, Element theElement, String theAttributeName) {
final String attributeValue = theElement.getAttributeValue(theAttributeName);
@ -587,6 +450,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
Context context = new Context();
context.setVariable("fhirVersion", myContext.getVersion().getVersion().name());
context.setVariable("resource", value);
String name = null;
@ -599,10 +463,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
if (name == null) {
if (value instanceof IBaseResource) {
name = myFhirContext.getResourceDefinition((IBaseResource)value).getName();
name = myContext.getResourceDefinition((Class<? extends IBaseResource>) value).getName();
} else if (value instanceof IDatatype) {
name = value.getClass().getSimpleName();
name = name.substring(0, name.length() - 2);
} else if (value instanceof IBaseDatatype) {
name = value.getClass().getSimpleName();
if (name.endsWith("Type")) {
name = name.substring(0, name.length() - 4);
}
} else {
throw new DataFormatException("Don't know how to determine name for type: " + value.getClass());
}
@ -677,20 +546,4 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
}
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

@ -25,7 +25,6 @@ import java.util.List;
import ca.uhn.fhir.rest.server.RestfulServer;
public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGenerator implements INarrativeGenerator {
public static final String NARRATIVES_PROPERTIES = "classpath:ca/uhn/fhir/narrative/narratives.properties";
@ -35,7 +34,7 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
@Override
protected List<String> getPropertyFile() {
List<String> retVal=new ArrayList<String>();
List<String> retVal = new ArrayList<String>();
retVal.add(NARRATIVES_PROPERTIES);
if (myUseHapiServerConformanceNarrative) {
retVal.add(HAPISERVER_NARRATIVES_PROPERTIES);
@ -44,25 +43,19 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe
}
/**
* If set to <code>true</code> (default is <code>false</code>) a special custom narrative for the
* Conformance resource will be provided, which is designed to be used with
* HAPI {@link RestfulServer} instances. This narrative provides a friendly search
* page which can assist users of the service.
* If set to <code>true</code> (default is <code>false</code>) a special custom narrative for the Conformance resource will be provided, which is designed to be used with HAPI {@link RestfulServer}
* instances. This narrative provides a friendly search page which can assist users of the service.
*/
public void setUseHapiServerConformanceNarrative(boolean theValue) {
myUseHapiServerConformanceNarrative=theValue;
myUseHapiServerConformanceNarrative = theValue;
}
/**
* If set to <code>true</code> (default is <code>false</code>) a special custom narrative for the
* Conformance resource will be provided, which is designed to be used with
* HAPI {@link RestfulServer} instances. This narrative provides a friendly search
* page which can assist users of the service.
* If set to <code>true</code> (default is <code>false</code>) a special custom narrative for the Conformance resource will be provided, which is designed to be used with HAPI {@link RestfulServer}
* instances. This narrative provides a friendly search page which can assist users of the service.
*/
public boolean isUseHapiServerConformanceNarrative() {
return myUseHapiServerConformanceNarrative;
}
}

View File

@ -21,24 +21,12 @@ package ca.uhn.fhir.narrative;
*/
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.INarrative;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
import ca.uhn.fhir.parser.DataFormatException;
public interface INarrativeGenerator {
void generateNarrative(String theProfile, IBaseResource theResource, BaseNarrativeDt<?> theNarrative) throws DataFormatException;
void generateNarrative(IBaseResource theResource, BaseNarrativeDt<?> theNarrative);
String generateTitle(IBaseResource theResource);
String generateTitle(String theProfile, IBaseResource theResource);
/**
* This method is called automatically by the framework, you do not need to interact with this method.
*/
void setFhirContext(FhirContext theFhirContext);
void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative);
}

View File

@ -62,7 +62,9 @@ import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
import org.hl7.fhir.instance.model.api.IBaseIntegerDatatype;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
@ -504,7 +506,7 @@ public class JsonParser extends BaseParser implements IParser {
}
case PRIMITIVE_XHTML_HL7ORG:
case PRIMITIVE_XHTML: {
if (!getSuppressNarratives()) {
if (!isSuppressNarratives()) {
IPrimitiveType<?> dt = (IPrimitiveType<?>) theNextValue;
if (theChildName != null) {
theWriter.write(theChildName, dt.getValueAsString());
@ -539,10 +541,17 @@ public class JsonParser extends BaseParser implements IParser {
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
if (nextChild instanceof RuntimeChildNarrativeDefinition) {
INarrativeGenerator gen = myContext.getNarrativeGenerator();
if (gen != null && theResource instanceof IResource) {
BaseNarrativeDt<?> narr = ((IResource) theResource).getText();
if (narr.getDiv().isEmpty()) {
gen.generateNarrative(theResDef.getResourceProfile(), theResource, narr);
if (gen != null) {
INarrative narr;
if (theResource instanceof IResource) {
narr = ((IResource) theResource).getText();
} else if (theResource instanceof IDomainResource) {
narr = ((IDomainResource)theResource).getText();
} else {
narr = null;
}
if (narr != null && narr.isEmpty()) {
gen.generateNarrative(myContext, theResource, narr);
if (narr != null && !narr.isEmpty()) {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());

View File

@ -56,7 +56,9 @@ import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IBaseXhtml;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
@ -576,21 +578,23 @@ public class XmlParser extends BaseParser implements IParser {
if (nextChild instanceof RuntimeChildNarrativeDefinition) {
INarrativeGenerator gen = myContext.getNarrativeGenerator();
INarrative narr;
if (theResource instanceof IResource) {
BaseNarrativeDt<?> narr = ((IResource) theResource).getText();
if (gen != null && narr.isEmpty()) {
String resourceProfile = myContext.getResourceDefinition(theResource).getResourceProfile();
gen.generateNarrative(resourceProfile, theResource, narr);
}
if (narr.isEmpty() == false) {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem);
continue;
}
narr = ((IResource) theResource).getText();
} else if (theResource instanceof IDomainResource) {
narr = ((IDomainResource)theResource).getText();
} else {
// Narrative generation not currently supported for HL7org structures
narr = null;
}
if (gen != null && narr.isEmpty()) {
gen.generateNarrative(myContext, theResource, narr);
}
if (narr != null && narr.isEmpty() == false) {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem);
continue;
}
}

View File

@ -27,14 +27,14 @@ import ca.uhn.fhir.parser.IParser;
public enum EncodingEnum {
XML(Constants.CT_FHIR_XML, Constants.CT_ATOM_XML, "application/xml", Constants.FORMAT_XML) {
XML(Constants.CT_FHIR_XML, Constants.CT_ATOM_XML, Constants.FORMAT_XML) {
@Override
public IParser newParser(FhirContext theContext) {
return theContext.newXmlParser();
}
},
JSON(Constants.CT_FHIR_JSON, Constants.CT_FHIR_JSON, Constants.CT_JSON, Constants.FORMAT_JSON) {
JSON(Constants.CT_FHIR_JSON, Constants.CT_FHIR_JSON, Constants.FORMAT_JSON) {
@Override
public IParser newParser(FhirContext theContext) {
return theContext.newJsonParser();
@ -50,7 +50,6 @@ public enum EncodingEnum {
for (EncodingEnum next : values()) {
ourContentTypeToEncoding.put(next.getBundleContentType(), next);
ourContentTypeToEncoding.put(next.getResourceContentType(), next);
ourContentTypeToEncoding.put(next.getBrowserFriendlyBundleContentType(), next);
}
/*
@ -67,13 +66,11 @@ public enum EncodingEnum {
private String myResourceContentType;
private String myBundleContentType;
private String myBrowserFriendlyContentType;
private String myFormatContentType;
EncodingEnum(String theResourceContentType, String theBundleContentType, String theBrowserFriendlyContentType, String theFormatContentType) {
EncodingEnum(String theResourceContentType, String theBundleContentType, String theFormatContentType) {
myResourceContentType = theResourceContentType;
myBundleContentType = theBundleContentType;
myBrowserFriendlyContentType = theBrowserFriendlyContentType;
myFormatContentType = theFormatContentType;
}
@ -91,10 +88,6 @@ public enum EncodingEnum {
return myResourceContentType;
}
public String getBrowserFriendlyBundleContentType() {
return myBrowserFriendlyContentType;
}
public static EncodingEnum forContentType(String theContentType) {
return ourContentTypeToEncoding.get(theContentType);
}

View File

@ -43,15 +43,14 @@ public abstract class RestfulResponse<T extends RequestDetails> implements IRest
public final Object streamResponseAsResource(IBaseResource resource, boolean prettyPrint, Set<SummaryEnum> summaryMode,
int statusCode, boolean respondGzip, boolean addContentLocationHeader)
throws IOException {
return RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), resource, prettyPrint, summaryMode, statusCode, respondGzip, addContentLocationHeader,
respondGzip, getRequestDetails());
return RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), resource, summaryMode, statusCode, addContentLocationHeader, respondGzip, getRequestDetails());
}
@Override
public Object streamResponseAsBundle(Bundle bundle, Set<SummaryEnum> summaryMode, boolean respondGzip, boolean requestIsBrowser)
throws IOException {
return RestfulServerUtils.streamResponseAsBundle(theRequestDetails.getServer(), bundle, summaryMode, requestIsBrowser, respondGzip, getRequestDetails());
return RestfulServerUtils.streamResponseAsBundle(theRequestDetails.getServer(), bundle, summaryMode, respondGzip, getRequestDetails());
}
@Override

View File

@ -72,6 +72,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.UrlUtil;
@ -112,9 +113,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
private boolean myUseBrowserFriendlyContentTypes;
/**
* Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or
* through {@link #setFhirContext(FhirContext)}) the server will determine which version of FHIR to support through
* classpath scanning. This is brittle, and it is highly recommended to explicitly specify a FHIR version.
* Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or through {@link #setFhirContext(FhirContext)}) the server will determine which
* version of FHIR to support through classpath scanning. This is brittle, and it is highly recommended to explicitly specify a FHIR version.
*/
public RestfulServer() {
this(null);
@ -137,8 +137,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/**
* This method is called prior to sending a response to incoming requests. It is used to add custom headers.
* <p>
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
* inadvertantly disabling functionality.
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling functionality.
* </p>
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
@ -375,9 +374,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either
* with the <code>_format</code> URL parameter, or with an <code>Accept</code> header in the request. The default is
* {@link EncodingEnum#XML}. Will not return null.
* Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header
* in the request. The default is {@link EncodingEnum#XML}. Will not return null.
*/
@Override
public EncodingEnum getDefaultResponseEncoding() {
@ -390,8 +388,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to creating their own.
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to
* creating their own.
*/
@Override
public FhirContext getFhirContext() {
@ -428,8 +426,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
* implementation
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path implementation
*
* @param requestFullPath
* the full request path
@ -455,8 +452,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public IServerAddressStrategy getServerAddressStrategy() {
return myServerAddressStrategy;
@ -476,19 +472,17 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Returns the method bindings for this server which are not specific to any particular resource type. This method is
* internal to HAPI and developers generally do not need to interact with it. Use with caution, as it may change.
* Returns the method bindings for this server which are not specific to any particular resource type. This method is internal to HAPI and developers generally do not need to interact with it. Use
* with caution, as it may change.
*/
public List<BaseMethodBinding<?>> getServerBindings() {
return myServerBinding.getMethodBindings();
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement if one has been explicitly defined.
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement if one has been explicitly defined.
* <p>
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or
* set to <code>null</code> to use the appropriate one for the given FHIR version.
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> to use the appropriate one for the given FHIR version.
* </p>
*/
public Object getServerConformanceProvider() {
@ -496,8 +490,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*
* @see RestfulServer#setServerName(String)
*/
@ -510,8 +503,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*/
public String getServerVersion() {
return myServerVersion;
@ -621,12 +613,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/*
* Actualy invoke the server method. This call is to a HAPI method binding, which is an object that wraps a
* specific implementing (user-supplied) method, but handles its input and provides its output back to the
* client.
* Actualy invoke the server method. This call is to a HAPI method binding, which is an object that wraps a specific implementing (user-supplied) method, but handles its input and provides
* its output back to the client.
*
* This is basically the end of processing for a successful request, since the method binding replies to the
* client and closes the response.
* This is basically the end of processing for a successful request, since the method binding replies to the client and closes the response.
*/
resourceMethod.invokeServer(this, requestDetails);
@ -660,12 +650,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} catch (Throwable e) {
/*
* We have caught an exception during request processing. This might be because a handling method threw
* something they wanted to throw (e.g. UnprocessableEntityException because the request had business
* requirement problems) or it could be due to bugs (e.g. NullPointerException).
* We have caught an exception during request processing. This might be because a handling method threw something they wanted to throw (e.g. UnprocessableEntityException because the request
* had business requirement problems) or it could be due to bugs (e.g. NullPointerException).
*
* First we let the interceptors have a crack at converting the exception into something HAPI can use
* (BaseServerResponseException)
* First we let the interceptors have a crack at converting the exception into something HAPI can use (BaseServerResponseException)
*/
BaseServerResponseException exception = null;
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
@ -678,8 +666,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/*
* If none of the interceptors converted the exception, default behaviour is to keep the exception as-is if it
* extends BaseServerResponseException, otherwise wrap it in an InternalErrorException.
* If none of the interceptors converted the exception, default behaviour is to keep the exception as-is if it extends BaseServerResponseException, otherwise wrap it in an
* InternalErrorException.
*/
if (exception == null) {
exception = DEFAULT_EXCEPTION_HANDLER.preProcessOutgoingException(requestDetails, e, theRequest);
@ -711,9 +699,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
* but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
* initialization of the restful server's internal init.
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is
* called immediately before beginning initialization of the restful server's internal init.
*/
@Override
public final void init() throws ServletException {
@ -782,13 +769,11 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
* server being used.
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
*
* @throws ServletException
* If the initialization failed. Note that you should consider throwing {@link UnavailableException}
* (which extends {@link ServletException}), as this is a flag to the servlet container that the servlet
* is not usable.
* If the initialization failed. Note that you should consider throwing {@link UnavailableException} (which extends {@link ServletException}), as this is a flag to the servlet container
* that the servlet is not usable.
*/
protected void initialize() throws ServletException {
// nothing by default
@ -845,8 +830,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Should the server "pretty print" responses by default (requesting clients can always override this default by
* supplying an <code>Accept</code> header in the request, or a <code>_pretty</code> parameter in the request URL.
* Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
* </p>
@ -859,14 +844,18 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this
* should be set to <code>true</code> unless the server has other configuration to deal with decompressing request
* bodies (e.g. a filter applied to the whole server).
* Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this should be set to <code>true</code> unless the server has other configuration to
* deal with decompressing request bodies (e.g. a filter applied to the whole server).
*/
public boolean isUncompressIncomingContents() {
return myUncompressIncomingContents;
}
/**
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor} instead as an interceptor on your server and it will provide more useful syntax
* highlighting. Deprocated in 1.4
*/
@Deprecated
@Override
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes;
@ -946,8 +935,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Register a single provider. This could be a Resource Provider or a "plain" provider not associated with any
* resource.
* Register a single provider. This could be a Resource Provider or a "plain" provider not associated with any resource.
*
* @param provider
* @throws Exception
@ -1005,7 +993,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
if (myTypeToProvider.containsKey(resourceName)) {
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName() + "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]");
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName()
+ "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]");
}
if (!inInit) {
myResourceProviders.add(rsrcProvider);
@ -1118,9 +1107,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
* (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
* being returned.
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} (which is the default), the server will automatically add a profile tag based on
* the class of the resource(s) being returned.
*
* @param theAddProfileTag
* The behaviour enum (must not be null)
@ -1141,8 +1129,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Should the server "pretty print" responses by default (requesting clients can always override this default by
* supplying an <code>Accept</code> header in the request, or a <code>_pretty</code> parameter in the request URL.
* Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
* </p>
@ -1155,12 +1143,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with
* the <code>_format</code> URL parameter, or with an <code>Accept</code> header in the request. The default is
* {@link EncodingEnum#XML}.
* Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header in
* the request. The default is {@link EncodingEnum#XML}.
* <p>
* Note when testing this feature: Some browsers will include "application/xml" in their Accept header, which means
* that the
* Note when testing this feature: Some browsers will include "application/xml" in their Accept header, which means that the
* </p>
*/
public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
@ -1169,8 +1155,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
* {@link #DEFAULT_ETAG_SUPPORT}
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is {@link #DEFAULT_ETAG_SUPPORT}
*
* @param theETagSupport
* The ETag support mode
@ -1278,8 +1263,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
@ -1287,17 +1271,15 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement.
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
* <p>
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be
* changed, or set to <code>null</code> if you do not wish to export a conformance statement.
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> if you do not wish to export a conformance
* statement.
* </p>
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
* Note that this method can only be called prior to {@link #init() initialization} and will throw an
* {@link IllegalStateException} if called after that.
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
*/
public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) {
@ -1320,34 +1302,32 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*/
public void setServerName(String theServerName) {
myServerName = theServerName;
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*/
public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion;
}
/**
* Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this
* should be set to <code>true</code> unless the server has other configuration to deal with decompressing request
* bodies (e.g. a filter applied to the whole server).
* Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this should be set to <code>true</code> unless the server has other configuration to
* deal with decompressing request bodies (e.g. a filter applied to the whole server).
*/
public void setUncompressIncomingContents(boolean theUncompressIncomingContents) {
myUncompressIncomingContents = theUncompressIncomingContents;
}
/**
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
* standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor} instead as an interceptor on your server and it will provide more useful syntax
* highlighting. Deprocated in 1.4
*/
@Deprecated
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
}

View File

@ -545,8 +545,7 @@ public class RestfulServerUtils {
return prettyPrint;
}
public static Object streamResponseAsBundle(IRestfulServerDefaults theServer, Bundle bundle, Set<SummaryEnum> theSummaryMode,
boolean theRequestIsBrowser, boolean respondGzip, RequestDetails theRequestDetails)
public static Object streamResponseAsBundle(IRestfulServerDefaults theServer, Bundle bundle, Set<SummaryEnum> theSummaryMode, boolean respondGzip, RequestDetails theRequestDetails)
throws IOException {
int status = 200;
@ -554,12 +553,7 @@ public class RestfulServerUtils {
// Determine response encoding
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails);
String contentType;
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
contentType = responseEncoding.getBrowserFriendlyBundleContentType();
} else {
contentType = responseEncoding.getBundleContentType();
}
String contentType = responseEncoding.getBundleContentType();
String charset = Constants.CHARSET_NAME_UTF8;
Writer writer = theRequestDetails.getResponse().getResponseWriter(status, contentType, charset, respondGzip);
@ -577,8 +571,8 @@ public class RestfulServerUtils {
return theRequestDetails.getResponse().sendWriterResponse(status, contentType, charset, writer);
}
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, boolean theRequestIsBrowser, Set<SummaryEnum> theSummaryMode,
int stausCode, boolean theRespondGzip, boolean theAddContentLocationHeader, boolean respondGzip,
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode,
int stausCode, boolean theAddContentLocationHeader, boolean respondGzip,
RequestDetails theRequestDetails)
throws IOException {
IRestfulResponse restUtil = theRequestDetails.getResponse();
@ -637,9 +631,7 @@ public class RestfulServerUtils {
}
}
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
contentType = responseEncoding.getBrowserFriendlyBundleContentType();
} else if (encodingDomainResourceAsText) {
if (encodingDomainResourceAsText) {
contentType = Constants.CT_HTML;
} else {
contentType = responseEncoding.getResourceContentType();
@ -668,18 +660,15 @@ public class RestfulServerUtils {
}
Writer writer = restUtil.getResponseWriter(stausCode, contentType, charset, respondGzip);
try {
if (encodingDomainResourceAsText && theResource instanceof IResource) {
writer.append(((IResource) theResource).getText().getDiv().getValueAsString());
} else {
IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails);
parser.encodeResourceToWriter(theResource, writer);
}
} catch (Exception e) {
//always send a response, even if the parsing went wrong
}
return restUtil.sendWriterResponse(stausCode, contentType, charset, writer);
if (encodingDomainResourceAsText && theResource instanceof IResource) {
writer.append(((IResource) theResource).getText().getDiv().getValueAsString());
} else {
IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails);
parser.encodeResourceToWriter(theResource, writer);
}
return restUtil.sendWriterResponse(stausCode, contentType, charset, writer);
}
// static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {

View File

@ -61,12 +61,12 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
private Integer myAddResponseIssueHeaderOnSeverity = null;
private Integer myAddResponseOutcomeHeaderOnSeverity = null;
private Integer myFailOnSeverity = ResultSeverityEnum.ERROR.ordinal();
private int myMaximumHeaderLength = 200;
private String myResponseIssueHeaderName = provideDefaultResponseHeaderName();
private String myResponseIssueHeaderValue = DEFAULT_RESPONSE_HEADER_VALUE;
private String myResponseIssueHeaderValueNoIssues = null;
private String myResponseOutcomeHeaderName = provideDefaultResponseHeaderName();
private List<IValidatorModule> myValidatorModules;
private void addResponseIssueHeader(RequestDetails theRequestDetails, SingleValidationMessage theNext) {
// Perform any string substitutions from the message format
StrLookup<?> lookup = new MyLookup(theNext);
@ -78,7 +78,6 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
theRequestDetails.getResponse().addHeader(myResponseIssueHeaderName, headerValue);
}
public BaseValidatingInterceptor<T> addValidatorModule(IValidatorModule theModule) {
Validate.notNull(theModule, "theModule must not be null");
if (getValidatorModules() == null) {
@ -98,6 +97,23 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
throw new UnprocessableEntityException(theRequestDetails.getServer().getFhirContext(), theValidationResult.toOperationOutcome());
}
/**
* If the validation produces a result with at least the given severity, a header with the name
* specified by {@link #setResponseOutcomeHeaderName(String)} will be added containing a JSON encoded
* OperationOutcome resource containing the validation results.
*/
public ResultSeverityEnum getAddResponseOutcomeHeaderOnSeverity() {
return myAddResponseOutcomeHeaderOnSeverity != null ? ResultSeverityEnum.values()[myAddResponseOutcomeHeaderOnSeverity] : null;
}
/**
* The maximum length for an individual header. If an individual header would be written exceeding this length,
* the header value will be truncated.
*/
public int getMaximumHeaderLength() {
return myMaximumHeaderLength;
}
/**
* The name of the header specified by {@link #setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum)}
*/
@ -109,15 +125,6 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
return myValidatorModules;
}
/**
* If the validation produces a result with at least the given severity, a header with the name
* specified by {@link #setResponseOutcomeHeaderName(String)} will be added containing a JSON encoded
* OperationOutcome resource containing the validation results.
*/
public ResultSeverityEnum getAddResponseOutcomeHeaderOnSeverity() {
return myAddResponseOutcomeHeaderOnSeverity != null ? ResultSeverityEnum.values()[myAddResponseOutcomeHeaderOnSeverity] : null;
}
abstract String provideDefaultResponseHeaderName();
/**
@ -148,6 +155,15 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
myFailOnSeverity = theSeverity != null ? theSeverity.ordinal() : null;
}
/**
* The maximum length for an individual header. If an individual header would be written exceeding this length,
* the header value will be truncated. Value must be greater than 100.
*/
public void setMaximumHeaderLength(int theMaximumHeaderLength) {
Validate.isTrue(theMaximumHeaderLength >= 100, "theMaximumHeadeerLength must be >= 100");
myMaximumHeaderLength = theMaximumHeaderLength;
}
/**
* Sets the name of the response header to add validation failures to
*
@ -271,6 +287,9 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
if (outcome != null) {
IParser parser = theRequestDetails.getServer().getFhirContext().newJsonParser().setPrettyPrint(false);
String encoded = parser.encodeResourceToString(outcome);
if (encoded.length() > getMaximumHeaderLength()) {
encoded = encoded.substring(0, getMaximumHeaderLength() - 3) + "...";
}
theRequestDetails.getResponse().addHeader(myResponseOutcomeHeaderName, encoded);
}
}

View File

@ -1,11 +1,14 @@
package ca.uhn.fhir.util;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -37,8 +40,7 @@ public class UrlUtil {
}
/**
* Resolve a relative URL - THIS METHOD WILL NOT FAIL but will log a warning and return theEndpoint if the input is
* invalid.
* Resolve a relative URL - THIS METHOD WILL NOT FAIL but will log a warning and return theEndpoint if the input is invalid.
*/
public static String constructAbsoluteUrl(String theBase, String theEndpoint) {
if (theEndpoint == null) {
@ -174,45 +176,61 @@ public class UrlUtil {
//@formatter:on
public static UrlParts parseUrl(String theUrl) {
String url = theUrl;
if (url.matches("\\/[a-zA-Z]+\\?.*")) {
url = url.substring(1);
}
UrlParts retVal = new UrlParts();
int nextStart = 0;
boolean nextIsHistory = false;
for (int idx = 0; idx < url.length(); idx++) {
char nextChar = url.charAt(idx);
boolean atEnd = (idx + 1) == url.length();
if (nextChar == '?' || nextChar == '/' || atEnd) {
int endIdx = (atEnd && nextChar != '?') ? idx + 1 : idx;
String nextSubstring = url.substring(nextStart, endIdx);
if (retVal.getResourceType() == null) {
retVal.setResourceType(nextSubstring);
} else if (retVal.getResourceId() == null) {
retVal.setResourceId(nextSubstring);
} else if (nextIsHistory) {
retVal.setVersionId(nextSubstring);
} else {
if (nextSubstring.equals(Constants.URL_TOKEN_HISTORY)) {
nextIsHistory = true;
} else {
throw new InvalidRequestException("Invalid FHIR resource URL: " + url);
}
}
if (nextChar == '?') {
if (url.length() > idx + 1) {
retVal.setParams(url.substring(idx + 1, url.length()));
}
break;
}
nextStart = idx + 1;
if (url.startsWith("http")) {
if (url.startsWith("/")) {
url = url.substring(1);
}
}
return retVal;
int qmIdx = url.indexOf('?');
if (qmIdx != -1) {
retVal.setParams(defaultIfBlank(url.substring(qmIdx + 1), null));
url = url.substring(0, qmIdx);
}
IdDt id = new IdDt(url);
retVal.setResourceType(id.getResourceType());
retVal.setResourceId(id.getIdPart());
retVal.setVersionId(id.getVersionIdPart());
return retVal;
} else {
if (url.matches("\\/[a-zA-Z]+\\?.*")) {
url = url.substring(1);
}
int nextStart = 0;
boolean nextIsHistory = false;
for (int idx = 0; idx < url.length(); idx++) {
char nextChar = url.charAt(idx);
boolean atEnd = (idx + 1) == url.length();
if (nextChar == '?' || nextChar == '/' || atEnd) {
int endIdx = (atEnd && nextChar != '?') ? idx + 1 : idx;
String nextSubstring = url.substring(nextStart, endIdx);
if (retVal.getResourceType() == null) {
retVal.setResourceType(nextSubstring);
} else if (retVal.getResourceId() == null) {
retVal.setResourceId(nextSubstring);
} else if (nextIsHistory) {
retVal.setVersionId(nextSubstring);
} else {
if (nextSubstring.equals(Constants.URL_TOKEN_HISTORY)) {
nextIsHistory = true;
} else {
throw new InvalidRequestException("Invalid FHIR resource URL: " + url);
}
}
if (nextChar == '?') {
if (url.length() > idx + 1) {
retVal.setParams(url.substring(idx + 1, url.length()));
}
break;
}
nextStart = idx + 1;
}
}
return retVal;
}
}
public static class UrlParts {

View File

@ -61,4 +61,8 @@ public class VersionUtil {
}
}
}
public static void main(String[] args) {
getVersion();
}
}

View File

@ -31,4 +31,8 @@ public interface INarrative extends ICompositeType {
// TODO: use less broad exception type here
public String getDivAsString() throws Exception;
public INarrative setStatusAsString(String theString);
public String getStatusAsString();
}

View File

@ -47,7 +47,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedNoId=Failed to {0}
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionContainsMultipleWithDuplicateId=Transaction bundle contains multiple resources with ID: {0}
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionEntryHasInvalidVerb=Transaction bundle entry has missing or invalid HTTP Verb specified in Bundle.entry.request.method. Found value: "{0}"
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionEntryHasInvalidVerb=Transaction bundle entry has missing or invalid HTTP Verb specified in Bundle.entry({1}).request.method. Found value: "{0}"
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionMissingUrl=Unable to perform {0}, no URL provided.
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionInvalidUrl=Unable to perform {0}, URL provided is invalid: {1}

View File

@ -16,13 +16,13 @@
<th:block th:switch="${fhirVersion}">
<th:block th:case="'DSTU1'">
<th:block th:if="${not resource.name.textElement.empty}" th:text="${resource.name.textElement.value}"/>
<th:block th:if=" ${resource.name.textElement.empty} and ${not resource.name.codingFirstRep.displayElement.empty}" th:text="${resource.name.codingFirstRep.display}"/>
<th:block th:if= "${resource.name.textElement.empty} and ${resource.name.codingFirstRep.displayElement.empty}" th:text="Untitled Diagnostic Report"/>
<th:block th:if=" ${resource.name.textElement.empty} and ${not resource.name.coding[0].displayElement.empty}" th:text="${resource.name.coding[0].display}"/>
<th:block th:if= "${resource.name.textElement.empty} and ${resource.name.coding[0].displayElement.empty}" th:text="Untitled Diagnostic Report"/>
</th:block>
<th:block th:case="*">
<th:block th:if="${not resource.code.textElement.empty}" th:text="${resource.code.textElement.value}"/>
<th:block th:if=" ${resource.code.textElement.empty} and ${not resource.code.codingFirstRep.displayElement.empty}" th:text="${resource.code.codingFirstRep.display}"/>
<th:block th:if= "${resource.code.textElement.empty} and ${resource.code.codingFirstRep.displayElement.empty}" th:text="Untitled Diagnostic Report"/>
<th:block th:if="${not resource.code.textElement.empty} or ${resource.code.coding.empty}" th:text="${resource.code.textElement.value}"/>
<th:block th:if="${not resource.code.coding.empty} and ${resource.code.textElement.empty} and ${not resource.code.coding[0].displayElement.empty}" th:text="${resource.code.coding[0].display}"/>
<th:block th:if="${not resource.code.coding.empty} and ${resource.code.textElement.empty} and ${resource.code.coding[0].displayElement.empty}" th:text="Untitled Diagnostic Report"/>
</th:block>
</th:block>
<!--/*--> Complete Blood Count <!--*/-->
@ -76,7 +76,7 @@
<td th:narrative="${result.resource.value}">2.2 g/L</td>
<td>
<th:block th:if="${not result.resource.interpretation.textElement.empty}" th:text="${result.resource.interpretation.text}"/>
<th:block th:if="${result.resource.interpretation.textElement.empty} and ${not result.resource.interpretation.codingFirstRep.displayElement.empty}" th:text="${result.resource.interpretation.codingFirstRep.display}"/>
<th:block th:if="${result.resource.interpretation.textElement.empty} and ${not result.resource.interpretation.coding.empty} and ${not result.resource.interpretation.coding[0].displayElement.empty}" th:text="${result.resource.interpretation.coding[0].display}"/>
<!--/*--> N <!--*/-->
</td>
<td>

View File

@ -11,16 +11,18 @@ a browser.
<!--*/-->
<div>
<div class="hapiHeaderText" th:if="${not resource.nameFirstRep.empty}" th:narrative="${resource.nameFirstRep}"/>
<th:block th:unless="${#lists.isEmpty(resource.name)}">
<div class="hapiHeaderText" th:narrative="${resource.name[0]}"/>
</th:block>
<table class="hapiPropertyTable">
<tbody>
<tr th:if="${not resource.identifierFirstRep.empty}">
<tr th:if="${not resource.identifier.empty}">
<td>Identifier</td>
<td th:narrative="${resource.identifierFirstRep}"></td>
<td th:narrative="${resource.identifier[0]}"></td>
</tr>
<tr th:if="${not resource.addressFirstRep.empty}">
<tr th:if="${not resource.address.empty}">
<td>Address</td>
<td th:narrative="${resource.addressFirstRep}"></td>
<td th:narrative="${resource.address[0]}"></td>
</tr>
<tr th:if="${not resource.birthDateElement.empty}">
<td>Date of birth</td>

View File

@ -1,11 +1,11 @@
<div>
<th:block th:if="${not resource.textElement.empty}" th:text="${resource.textElement.value}"/>
<th:block th:if="${resource.textElement.empty}">
<th:block th:if="${!resource.codingFirstRep.empty}">
<th:block th:if="${!resource.codingFirstRep.displayElement.empty}" th:text="${!resource.codingFirstRep.displayElement.value}"/>
<th:block th:if="${resource.codingFirstRep.displayElement.empty}">
<th:block th:if="${!resource.codingFirstRep.codeElement.empty}">
<th:block th:text="${resource.codingFirstRep.codeElement.value}"/>
<th:block th:if="${!resource.coding.empty}">
<th:block th:if="${!resource.coding[0].displayElement.empty}" th:text="${!resource.coding[0].displayElement.value}"/>
<th:block th:if="${resource.coding[0].displayElement.empty}">
<th:block th:if="${!resource.coding[0].codeElement.empty}">
<th:block th:text="${resource.coding[0].codeElement.value}"/>
</th:block>
</th:block>
</th:block>

View File

@ -1,5 +1,12 @@
<div>
<th:block th:if="${not resource.comparatorElement.empty}" th:text="${resource.comparatorElement.value}"/>
<th:block th:if="${not resource.valueElement.empty}" th:text="${resource.valueElement.valueAsString}"/>
<th:block th:if="${not resource.unitsElement.empty}" th:text="${resource.unitsElement.value}"/>
<th:block th:switch="${fhirVersion}">
<th:block th:case="'DSTU1'">
<th:block th:if="${not resource.unitsElement.empty}" th:text="${resource.unitsElement.value}"/>
</th:block>
<th:block th:case="*">
<th:block th:if="${not resource.unitElement.empty}" th:text="${resource.unitElement.value}"/>
</th:block>
</th:block>
</div>

View File

@ -1,4 +1,11 @@
<div>
<th:block th:if="${not resource.valueElement.empty}" th:text="${resource.valueElement.valueAsString}"/>
<th:block th:if="${not resource.unitsElement.empty}" th:text="${resource.unitsElement.value}"/>
<th:block th:switch="${fhirVersion}">
<th:block th:case="'DSTU1'">
<th:block th:if="${not resource.unitsElement.empty}" th:text="${resource.unitsElement.value}"/>
</th:block>
<th:block th:case="*">
<th:block th:if="${not resource.unitElement.empty}" th:text="${resource.unitElement.value}"/>
</th:block>
</th:block>
</div>

View File

@ -48,25 +48,19 @@ simplequantity.narrative=classpath:ca/uhn/fhir/narrative/datatype/SimpleQuantity
diagnosticreport.class=ca.uhn.fhir.model.dstu.resource.DiagnosticReport
diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html
diagnosticreport.title=classpath:ca/uhn/fhir/narrative/title/DiagnosticReport.html
encounter.class=ca.uhn.fhir.model.dstu.resource.Encounter
encounter.title=classpath:ca/uhn/fhir/narrative/title/Encounter.html
operationoutcome.class=ca.uhn.fhir.model.dstu.resource.OperationOutcome
operationoutcome.title=classpath:ca/uhn/fhir/narrative/title/OperationOutcome.html
operationoutcome.narrative=classpath:ca/uhn/fhir/narrative/OperationOutcome.html
organization.class=ca.uhn.fhir.model.dstu.resource.Organization
organization.title=classpath:ca/uhn/fhir/narrative/title/Organization.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
medicationprescription.class=ca.uhn.fhir.model.dstu.resource.MedicationPrescription
medicationprescription.narrative=classpath:ca/uhn/fhir/narrative/MedicationPrescription.html
medicationprescription.title=classpath:ca/uhn/fhir/narrative/title/MedicationPrescription.html
medication.class=ca.uhn.fhir.model.dstu.resource.Medication
medication.narrative=classpath:ca/uhn/fhir/narrative/Medication.html

View File

@ -1,17 +0,0 @@
<div>
<th:block th:switch="${fhirVersion}">
<th:block th:case="'DSTU1'">
<th:block th:if="${not resource.name.textElement.empty}" th:text="${resource.name.textElement.value}"/>
<th:block th:if=" ${resource.name.textElement.empty} and ${not resource.name.codingFirstRep.displayElement.empty}" th:text="${resource.name.codingFirstRep.display}"/>
<th:block th:if= "${resource.name.textElement.empty} and ${resource.name.codingFirstRep.displayElement.empty}" th:text="Untitled Diagnostic Report"/>
</th:block>
<th:block th:case="*">
<th:block th:if="${not resource.code.textElement.empty}" th:text="${resource.code.textElement.value}"/>
<th:block th:if=" ${resource.code.textElement.empty} and ${not resource.code.codingFirstRep.displayElement.empty}" th:text="${resource.code.codingFirstRep.display}"/>
<th:block th:if= "${resource.code.textElement.empty} and ${resource.code.codingFirstRep.displayElement.empty}" th:text="Untitled Diagnostic Report"/>
</th:block>
</th:block>
<th:block th:if="${not resource.statusElement.empty}" th:text="' - ' + ${resource.statusElement.value}"/>
<th:block th:text="' - ' + ${resource.result.size} + ' observations'"/>
</div>

View File

@ -1,15 +0,0 @@
<div>
<th:block th:if="${!resource.identifierFirstRep.empty}" th:narrative="${resource.identifierFirstRep}" />
<th:block th:if="${not resource.statusElement.empty}">
/ <th:block th:narrative="${resource.statusElement}"/>
</th:block>
<th:block th:if="${not resource.typeFirstRep.empty}">
/ <th:block th:narrative="${resource.typeFirstRep}"/>
</th:block>
<th:block th:if="${not resource.classElementElement.empty}">
/ <th:block th:narrative="${resource.classElementElement}"/>
</th:block>
<th:block th:if="${not resource.period.empty}">
/ <th:block th:narrative="${resource.period}"/>
</th:block>
</div>

View File

@ -1,3 +0,0 @@
<div>
<div class="hapiHeaderText" th:if="${not resource.medication.resource.nameElement.empty}" th:narrative="${resource.medication.resource.nameElement}"></div>
</div>

View File

@ -1,5 +0,0 @@
<div>
Operation Outcome
<th:block th:if="${resource.issue.size} == 1" th:text="'(' + ${resource.issueFirstRep.severityElement.value} + ')'"/>
<th:block th:if="${resource.issue.size} != 1" th:text="'(' + ${resource.issue.size} + ' issues)'"/>
</div>

View File

@ -1,4 +0,0 @@
<div>
<th:block th:if="${not resource.nameElement.empty}" th:text="${resource.nameElement.value}"/>
<th:block th:if="${resource.nameElement.empty}">Unknown Organization</th:block>
</div>

View File

@ -1,6 +0,0 @@
<div>
<th:block th:narrative="${resource.nameFirstRep}" />
<th:block th:if="${not resource.identifierFirstRep.empty}">
(<th:block th:narrative="${resource.identifierFirstRep}">8708660</th:block>)
</th:block>
</div>

View File

@ -4,6 +4,8 @@ import static org.junit.Assert.*;
import org.junit.Test;
import ca.uhn.fhir.util.UrlUtil.UrlParts;
public class UrlUtilTest {
@Test
@ -23,6 +25,18 @@ public class UrlUtilTest {
assertFalse(UrlUtil.isValid(null));
}
@Test
public void testParseUrl() {
assertEquals("ConceptMap", UrlUtil.parseUrl("http://hl7.org/fhir/ConceptMap/ussgfht-loincde").getResourceType());
assertEquals("ConceptMap", UrlUtil.parseUrl("http://hl7.org/fhir/ConceptMap/ussgfht-loincde").getResourceType());
assertEquals("ussgfht-loincde", UrlUtil.parseUrl("http://hl7.org/fhir/ConceptMap/ussgfht-loincde").getResourceId());
assertEquals(null, UrlUtil.parseUrl("http://hl7.org/fhir/ConceptMap/ussgfht-loincde?").getParams());
assertEquals("a=b", UrlUtil.parseUrl("http://hl7.org/fhir/ConceptMap/ussgfht-loincde?a=b").getParams());
assertEquals("a=b", UrlUtil.parseUrl("ConceptMap/ussgfht-loincde?a=b").getParams());
}
@Test
public void testConstructAbsoluteUrl() {
assertEquals("http://foo/bar/baz", UrlUtil.constructAbsoluteUrl(null, "http://foo/bar/baz"));

View File

@ -18,7 +18,6 @@ import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.resource.StructureDefinition;
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.IGenericClient;
public class ValidationDataUploader extends BaseCommand {
@ -64,7 +63,8 @@ public class ValidationDataUploader extends BaseCommand {
String vsContents;
try {
vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/valueset/valuesets.xml"), "UTF-8");
ctx.getVersion().getPathToSchemaDefinitions();
vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/valueset/"+"valuesets.xml"), "UTF-8");
} catch (IOException e) {
throw new CommandFailureException(e.toString());
}
@ -83,7 +83,7 @@ public class ValidationDataUploader extends BaseCommand {
}
try {
vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/valueset/v3-codesystems.xml"), "UTF-8");
vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/valueset/"+"v3-codesystems.xml"), "UTF-8");
} catch (IOException e) {
throw new CommandFailureException(e.toString());
}
@ -102,7 +102,7 @@ public class ValidationDataUploader extends BaseCommand {
}
try {
vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/valueset/v2-tables.xml"), "UTF-8");
vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/valueset/"+"v2-tables.xml"), "UTF-8");
} catch (IOException e) {
throw new CommandFailureException(e.toString());
}
@ -123,7 +123,7 @@ public class ValidationDataUploader extends BaseCommand {
ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
Resource[] mappingLocations;
try {
mappingLocations = patternResolver.getResources("classpath*:org/hl7/fhir/instance/model/profile/*.profile.xml");
mappingLocations = patternResolver.getResources("classpath*:org/hl7/fhir/instance/model/profile/"+"*.profile.xml");
} catch (IOException e) {
throw new CommandFailureException(e.toString());
}

View File

@ -45,24 +45,13 @@ public class JaxRsResponseTest {
boolean theRequestIsBrowser = false;
boolean respondGzip = false;
Set<SummaryEnum> theSummaryMode = Collections.<SummaryEnum>emptySet();
Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request);
Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, respondGzip, request);
assertEquals(200, result.getStatus());
assertEquals(Constants.CT_FHIR_JSON+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
assertTrue(result.getEntity().toString().contains("Patient"));
assertTrue(result.getEntity().toString().contains("15"));
}
@Test
public void testGetResponseWriterBrowserNoZip() throws IOException {
boolean theRequestIsBrowser = true;
boolean respondGzip = false;
Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request);
assertEquals(200, result.getStatus());
assertEquals(Constants.CT_JSON+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
assertTrue(result.getEntity().toString().contains("Patient"));
assertTrue(result.getEntity().toString().contains("15"));
}
@Test
public void testSendAttachmentResponse() throws IOException {
boolean theRequestIsBrowser = true;
@ -73,7 +62,7 @@ public class JaxRsResponseTest {
binary.setContentType(contentType);
binary.setContent(content);
boolean theAddContentLocationHeader = false;
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theRequestIsBrowser, theSummaryMode, 200, respondGzip, theAddContentLocationHeader, respondGzip, this.request);
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theSummaryMode, 200, theAddContentLocationHeader, respondGzip, this.request);
assertEquals(200, result.getStatus());
assertEquals(contentType, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
assertEquals(content, result.getEntity());
@ -86,7 +75,7 @@ public class JaxRsResponseTest {
IBaseBinary binary = new Binary();
binary.setContent(new byte[]{});
boolean theAddContentLocationHeader = false;
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theRequestIsBrowser, theSummaryMode, 200, respondGzip, theAddContentLocationHeader, respondGzip, this.request);
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theSummaryMode, 200, theAddContentLocationHeader, respondGzip, this.request);
assertEquals(200, result.getStatus());
assertEquals(null, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
assertEquals(null, result.getEntity());
@ -98,7 +87,7 @@ public class JaxRsResponseTest {
boolean respondGzip = true;
IBaseBinary binary = new Binary();
boolean theAddContentLocationHeader = false;
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theRequestIsBrowser, theSummaryMode, 200, respondGzip, theAddContentLocationHeader, respondGzip, this.request);
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theSummaryMode, 200, theAddContentLocationHeader, respondGzip, this.request);
assertEquals(200, result.getStatus());
assertEquals(null, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
assertEquals(null, result.getEntity());

View File

@ -225,7 +225,7 @@
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<artifactId>javax.transaction-api</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>

View File

@ -91,7 +91,7 @@ public class BaseDstu21Config extends BaseConfig {
return module;
}
@Bean(autowire=Autowire.BY_NAME)
@Bean(autowire=Autowire.BY_NAME, name="myJpaValidationSupportChainDstu21")
public IValidationSupport validationSupportChainDstu21() {
return new JpaValidationSupportChainDstu21();
}

View File

@ -355,6 +355,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
nextEntity = new ResourceLink(nextPath, theEntity, target);
} else {
if (!multiType) {
if (nextSpDef.getName().equals("sourceuri")) {
continue; // TODO: disable this eventually - ConceptMap:sourceuri is of type reference but points to a URI
}
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
} else {
continue;

View File

@ -292,6 +292,15 @@ public class FhirSystemDaoDstu21 extends BaseHapiFhirSystemDao<Bundle, Meta> {
Map<IdType, IdType> idSubstitutions = new HashMap<IdType, IdType>();
Map<IdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<IdType, DaoMethodOutcome>();
// Do all entries have a verb?
for (int i = 0; i < theRequest.getEntry().size(); i++) {
BundleEntryComponent nextReqEntry = theRequest.getEntry().get(i);
HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue();
if (verb == null) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionEntryHasInvalidVerb", nextReqEntry.getRequest().getMethod(), i));
}
}
/*
* We want to execute the transaction request bundle elements in the order
* specified by the FHIR specification (see TransactionSorter) so we save the
@ -366,9 +375,6 @@ public class FhirSystemDaoDstu21 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue();
if (verb == null) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionEntryHasInvalidVerb", nextReqEntry.getRequest().getMethod()));
}
String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null;
BundleEntryComponent nextRespEntry = response.getEntry().get(originalRequestOrder.get(nextReqEntry));

View File

@ -9,6 +9,8 @@ import org.springframework.beans.factory.annotation.Qualifier;
public class JpaValidationSupportChainDstu21 extends ValidationSupportChain {
private DefaultProfileValidationSupport myDefaultProfileValidationSupport = new DefaultProfileValidationSupport();
@Autowired
@Qualifier("myJpaValidationSupportDstu21")
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu21 myJpaValidationSupportDstu21;
@ -17,15 +19,13 @@ public class JpaValidationSupportChainDstu21 extends ValidationSupportChain {
super();
}
private DefaultProfileValidationSupport myDefaultProfileValidationSupport = new DefaultProfileValidationSupport();
public void flush() {
myDefaultProfileValidationSupport.flush();
}
@PostConstruct
public void postConstruct() {
addValidationSupport(myDefaultProfileValidationSupport);
addValidationSupport(myJpaValidationSupportDstu21);
}
public void flush() {
myDefaultProfileValidationSupport.flush();
}
}

View File

@ -11,6 +11,7 @@ import javax.persistence.EntityManager;
import org.apache.commons.io.IOUtils;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.hl7.fhir.dstu21.hapi.validation.IValidationSupport;
import org.hl7.fhir.dstu21.model.Bundle;
import org.hl7.fhir.dstu21.model.CodeableConcept;
import org.hl7.fhir.dstu21.model.Coding;
@ -86,7 +87,9 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@ContextConfiguration(classes= {TestDstu21Config.class})
//@formatter:on
public abstract class BaseJpaDstu21Test extends BaseJpaTest {
@Autowired
@Qualifier("myJpaValidationSupportChainDstu21")
protected IValidationSupport myValidationSupport;
@Autowired
protected ApplicationContext myAppCtx;
@Autowired

View File

@ -875,6 +875,43 @@ public class FhirSystemDaoDstu21Test extends BaseJpaDstu21SystemTest {
assertEquals("Patient/temp6789", p.getLink().get(0).getOther().getReference());
}
@Test
public void testTransactionWithBundledValidationSourceAndTarget() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/questionnaire-sdc-profile-example-ussg-fht.xml");
String bundleStr = IOUtils.toString(bundleRes);
Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, bundleStr);
Bundle resp = mySystemDao.transaction(myRequestDetails, bundle);
String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp);
ourLog.info(encoded);
encoded = myFhirCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(resp);
//@formatter:off
assertThat(encoded, containsString("\"response\":{" +
"\"status\":\"201 Created\"," +
"\"location\":\"Questionnaire/54127-6/_history/1\","));
//@formatter:on
/*
* Upload again to update
*/
resp = mySystemDao.transaction(myRequestDetails, bundle);
encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp);
ourLog.info(encoded);
encoded = myFhirCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(resp);
//@formatter:off
assertThat(encoded, containsString("\"response\":{" +
"\"status\":\"200 OK\"," +
"\"location\":\"Questionnaire/54127-6/_history/2\","));
//@formatter:on
}
@Test
public void testTransactionFromBundle6() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle3.xml");

View File

@ -13,18 +13,24 @@ import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu21.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu21.model.Bundle;
import org.hl7.fhir.dstu21.model.Bundle.BundleType;
import org.hl7.fhir.dstu21.model.Bundle.HTTPVerb;
import org.hl7.fhir.dstu21.model.DecimalType;
import org.hl7.fhir.dstu21.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu21.model.IdType;
import org.hl7.fhir.dstu21.model.Observation;
import org.hl7.fhir.dstu21.model.OperationDefinition;
@ -33,6 +39,7 @@ import org.hl7.fhir.dstu21.model.Parameters;
import org.hl7.fhir.dstu21.model.Patient;
import org.hl7.fhir.dstu21.model.StringType;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
@ -41,16 +48,20 @@ import org.springframework.transaction.annotation.Transactional;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.dstu21.BaseJpaDstu21Test;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.jpa.rp.dstu21.ObservationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu21.OrganizationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu21.PatientResourceProvider;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
public class SystemProviderDstu21Test extends BaseJpaDstu21Test {
@ -113,6 +124,62 @@ public class SystemProviderDstu21Test extends BaseJpaDstu21Test {
}
}
@SuppressWarnings("deprecation")
@After
public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
}
@SuppressWarnings("deprecation")
@Test
public void testResponseUsesCorrectContentType() throws Exception {
myRestServer.setUseBrowserFriendlyContentTypes(true);
myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
HttpGet get = new HttpGet(ourServerBase);
// get.addHeader("Accept", "application/xml, text/html");
CloseableHttpResponse http = ourHttpClient.execute(get);
assertThat(http.getFirstHeader("Content-Type").getValue(), containsString("application/json+fhir"));
}
@Test
public void testValidateUsingIncomingResources() throws Exception {
FhirInstanceValidator val = new FhirInstanceValidator(myValidationSupport);
RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
interceptor.addValidatorModule(val);
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
interceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
myRestServer.registerInterceptor(interceptor);
try {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/questionnaire-sdc-profile-example-ussg-fht.xml");
String bundleStr = IOUtils.toString(bundleRes);
HttpPost req = new HttpPost(ourServerBase);
req.setEntity(new StringEntity(bundleStr, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8")));
CloseableHttpResponse resp = ourHttpClient.execute(req);
String encoded = IOUtils.toString(resp.getEntity().getContent());
IOUtils.closeQuietly(resp.getEntity().getContent());
ourLog.info(encoded);
//@formatter:off
assertThat(encoded, containsString("Questionnaire/54127-6/_history/"));
//@formatter:on
for (Header next : resp.getHeaders(RequestValidatingInterceptor.DEFAULT_RESPONSE_HEADER_NAME)) {
ourLog.info(next.toString());
}
} finally {
myRestServer.unregisterInterceptor(interceptor);
}
}
@Test
public void testEverythingReturnsCorrectFormatInPagingLink() throws Exception {
myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
@ -129,6 +196,7 @@ public class SystemProviderDstu21Test extends BaseJpaDstu21Test {
HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything");
get.addHeader("Accept", "application/xml, text/html");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
String response = IOUtils.toString(http.getEntity().getContent());
ourLog.info(response);
@ -282,6 +350,23 @@ public class SystemProviderDstu21Test extends BaseJpaDstu21Test {
ourLog.info(response);
}
@Test
public void testTransactionWithIncompleteBundle() throws Exception {
Patient patient = new Patient();
patient.setGender(AdministrativeGender.MALE);
Bundle bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
bundle.addEntry().setResource(patient);
try {
ourClient.transaction().withBundle(bundle).prettyPrint().execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.toString(), containsString(""));
}
}
@Test
public void testTransactionFromBundle2() throws Exception {

View File

@ -126,20 +126,6 @@ public class JpaServerDemo extends RestfulServer {
FhirContext ctx = getFhirContext();
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
/*
* This tells the server to use "browser friendly" MIME types if it
* detects that the request is coming from a browser, in the hopes that the
* browser won't just treat the content as a binary payload and try
* to download it (which is what generally happens if you load a
* FHIR URL in a browser).
*
* This means that the server isn't technically complying with the
* FHIR specification for direct browser requests, but this mode
* is very helpful for testing and troubleshooting since it means
* you can look at FHIR URLs directly in a browser.
*/
setUseBrowserFriendlyContentTypes(true);
/*
* Default to XML and pretty printing
*/

View File

@ -61,7 +61,6 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
myContext = theContext;
}
@Override
public void addResourcesToBundle(List<IBaseResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) {
if (myBundle == null) {
@ -78,7 +77,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
for (IBaseResource nextBaseRes : theResult) {
IResource next = (IResource)nextBaseRes;
IResource next = (IResource) nextBaseRes;
Set<String> containedIds = new HashSet<String>();
for (IResource nextContained : next.getContained().getContainedResources()) {
@ -87,23 +86,13 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
}
if (myContext.getNarrativeGenerator() != null) {
String title = myContext.getNarrativeGenerator().generateTitle(next);
ourLog.trace("Narrative generator created title: {}", title);
if (StringUtils.isNotBlank(title)) {
ResourceMetadataKeyEnum.TITLE.put(next, title);
}
} else {
ourLog.trace("No narrative generator specified");
}
List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
do {
List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
for (ResourceReferenceInfo nextRefInfo : references) {
if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes))
continue;
for (ResourceReferenceInfo nextRefInfo : references) {
if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes))
continue;
IResource nextRes = (IResource) nextRefInfo.getResourceReference().getResource();
if (nextRes != null) {
@ -128,7 +117,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
}
includedResources.addAll(addedResourcesThisPass);
includedResources.addAll(addedResourcesThisPass);
// Linked resources may themselves have linked resources
references = new ArrayList<ResourceReferenceInfo>();
@ -155,7 +144,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
@Override
public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, IPrimitiveType<Date> theLastUpdated) {
if (myBundle.getAuthorName().isEmpty()) {
myBundle.getAuthorName().setValue(theAuthor);
@ -197,7 +186,8 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void initializeBundleFromBundleProvider(IRestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes) {
public void initializeBundleFromBundleProvider(IRestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl,
boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes) {
int numToReturn;
String searchId = null;
List<IBaseResource> resourceList;
@ -255,7 +245,8 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) {
myBundle.getLinkNext().setValue(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType));
myBundle.getLinkNext()
.setValue(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType));
}
if (theOffset > 0) {
int start = Math.max(0, theOffset - limit);
@ -266,7 +257,8 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults, BundleTypeEnum theBundleType) {
public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults,
BundleTypeEnum theBundleType) {
myBundle = new Bundle();
myBundle.getAuthorName().setValue(theAuthor);
@ -294,16 +286,6 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
}
if (myContext.getNarrativeGenerator() != null) {
String title = myContext.getNarrativeGenerator().generateTitle(next);
ourLog.trace("Narrative generator created title: {}", title);
if (StringUtils.isNotBlank(title)) {
ResourceMetadataKeyEnum.TITLE.put(next, title);
}
} else {
ourLog.trace("No narrative generator specified");
}
List<BaseResourceReferenceDt> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, BaseResourceReferenceDt.class);
do {
List<IBaseResource> addedResourcesThisPass = new ArrayList<IBaseResource>();
@ -367,10 +349,9 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
throw new UnsupportedOperationException("DSTU1 server doesn't support resource style bundles");
}
@Override
public List<IBaseResource> toListOfResources() {
return new ArrayList<IBaseResource>( myBundle.toListOfResources());
return new ArrayList<IBaseResource>(myBundle.toListOfResources());
}
}

View File

@ -19,7 +19,6 @@ public class CustomThymeleafNarrativeGeneratorTest {
public void testGenerator() {
CustomThymeleafNarrativeGenerator gen = new CustomThymeleafNarrativeGenerator("classpath:narrative/customnarrative.properties");
gen.setFhirContext(ourCtx);
Practitioner p = new Practitioner();
p.addIdentifier("sys", "val1");
@ -28,7 +27,7 @@ public class CustomThymeleafNarrativeGeneratorTest {
p.getName().addFamily("fam1").addGiven("given");
NarrativeDt narrative = new NarrativeDt();
gen.generateNarrative(p, narrative);
gen.generateNarrative(ourCtx, p, narrative);
String actual = narrative.getDiv().getValueAsString();
ourLog.info(actual);

View File

@ -62,14 +62,10 @@ public class DefaultThymeleafNarrativeGeneratorTest {
value.setBirthDate(new Date(), TemporalPrecisionEnum.DAY);
NarrativeDt narrative = new NarrativeDt();
gen.generateNarrative(value, narrative);
gen.generateNarrative(myCtx, value, narrative);
String output = narrative.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);
// Removed because label is gone in DSTU2
// value.getIdentifierFirstRep().setLabel("FOO MRN 123");
// title = gen.generateTitle(value);
@ -78,6 +74,24 @@ public class DefaultThymeleafNarrativeGeneratorTest {
}
@Test
public void testGeneratePatientWithoutData() throws DataFormatException {
Patient value = new Patient();
NarrativeDt narrative = new NarrativeDt();
gen.generateNarrative(myCtx, value, narrative);
String output = narrative.getDiv().getValueAsString();
assertThat(output, StringContains.containsString("<div>"));
// Removed because label is gone in DSTU2
// value.getIdentifierFirstRep().setLabel("FOO MRN 123");
// title = gen.generateTitle(value);
// assertEquals("joe john BLOW (FOO MRN 123)", title);
// ourLog.info(title);
}
@Test
public void testGenerateEncounter() throws DataFormatException {
Encounter enc = new Encounter();
@ -87,11 +101,6 @@ public class DefaultThymeleafNarrativeGeneratorTest {
enc.setPeriod(new PeriodDt().setStart(new DateTimeDt("2001-01-02T11:11:00")));
enc.setType(EncounterTypeEnum.ANNUAL_DIABETES_MELLITUS_SCREENING);
String title = gen.generateTitle(enc);
title = title.replaceAll("00 [A-Z]+ 2001", "00 TZ 2001"); // account for whatever time zone
assertEquals("1234567 / ADMS / ambulatory / Tue Jan 02 11:11:00 TZ 2001 - ?", title);
ourLog.info(title);
}
@Test
@ -102,10 +111,6 @@ public class DefaultThymeleafNarrativeGeneratorTest {
enc.setName("Some Test Org");
enc.addAddress().addLine("123 Fake St").setCity("Toronto").setState("ON").setCountry("Canada").setZip("12345");
String title = gen.generateTitle(enc);
assertEquals("Some Test Org", title);
ourLog.info(title);
}
@Test
@ -113,7 +118,7 @@ public class DefaultThymeleafNarrativeGeneratorTest {
Conformance value = myCtx.newXmlParser().parseResource(Conformance.class, new InputStreamReader(getClass().getResourceAsStream("/server-conformance-statement.xml")));
NarrativeDt narrative = new NarrativeDt();
gen.generateNarrative(value, narrative);
gen.generateNarrative(myCtx, value, narrative);
String output =narrative.getDiv().getValueAsString();
ourLog.info(output);
@ -129,7 +134,7 @@ public class DefaultThymeleafNarrativeGeneratorTest {
value.addResult().setReference("Observation/3");
NarrativeDt narrative = new NarrativeDt();
gen.generateNarrative("http://hl7.org/fhir/profiles/DiagnosticReport", value, narrative);
gen.generateNarrative(myCtx, value, narrative);
String output = narrative.getDiv().getValueAsString();
ourLog.info(output);
@ -158,7 +163,7 @@ public class DefaultThymeleafNarrativeGeneratorTest {
// assertEquals("Operation Outcome (2 issues)", output);
NarrativeDt narrative = new NarrativeDt();
gen.generateNarrative(null, oo, narrative);
gen.generateNarrative(myCtx, oo, narrative);
String nar = narrative.getDiv().getValueAsString();
ourLog.info(nar);
@ -201,16 +206,12 @@ public class DefaultThymeleafNarrativeGeneratorTest {
}
NarrativeDt narrative = new NarrativeDt();
gen.generateNarrative("http://hl7.org/fhir/profiles/DiagnosticReport", value, narrative);
gen.generateNarrative(myCtx, value, narrative);
String output = narrative.getDiv().getValueAsString();
ourLog.info(output);
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> Some &amp; Diagnostic Report </div>"));
String title = gen.generateTitle(value);
// ourLog.info(title);
assertEquals("Some & Diagnostic Report - final - 3 observations", title);
// Now try it with the parser
output = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(value);
@ -231,13 +232,10 @@ public class DefaultThymeleafNarrativeGeneratorTest {
mp.setDateWritten(new DateTimeDt("2014-09-01"));
NarrativeDt narrative = new NarrativeDt();
gen.generateNarrative(mp, narrative);
gen.generateNarrative(myCtx, mp, narrative);
assertTrue("Expected medication name of ciprofloaxin within narrative: " + narrative.getDiv().toString(), narrative.getDiv().toString().indexOf("ciprofloaxin") > -1);
assertTrue("Expected string status of ACTIVE within narrative: " + narrative.getDiv().toString(), narrative.getDiv().toString().indexOf("ACTIVE") > -1);
String title = gen.generateTitle(mp);
assertEquals("ciprofloaxin", title);
}
}

View File

@ -25,6 +25,7 @@ import org.hamcrest.core.IsNot;
import org.hamcrest.core.StringContains;
import org.hamcrest.text.StringContainsInOrder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.INarrative;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@ -940,30 +941,15 @@ public class JsonParserTest {
INarrativeGenerator gen = new INarrativeGenerator() {
@Override
public void generateNarrative(IBaseResource theResource, BaseNarrativeDt<?> theNarrative) {
throw new UnsupportedOperationException();
public void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative) {
try {
theNarrative.setDivAsString("<div>help</div>");
} catch (Exception e) {
throw new Error(e);
}
theNarrative.setStatusAsString("generated");
}
@Override
public void generateNarrative(String theProfile, IBaseResource theResource, BaseNarrativeDt<?> theNarrative) throws DataFormatException {
theNarrative.getDiv().setValueAsString("<div>help</div>");
theNarrative.getStatus().setValueAsString("generated");
}
@Override
public String generateTitle(IBaseResource theResource) {
throw new UnsupportedOperationException();
}
@Override
public String generateTitle(String theProfile, IBaseResource theResource) {
throw new UnsupportedOperationException();
}
@Override
public void setFhirContext(FhirContext theFhirContext) {
// nothing
}
};
FhirContext context = ourCtx;

View File

@ -21,6 +21,7 @@ import org.hamcrest.core.IsNot;
import org.hamcrest.core.StringContains;
import org.hamcrest.text.StringContainsInOrder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.INarrative;
import org.junit.BeforeClass;
import org.junit.Test;
import org.xml.sax.SAXException;
@ -1246,30 +1247,15 @@ public class XmlParserTest {
INarrativeGenerator gen = new INarrativeGenerator() {
@Override
public void generateNarrative(IBaseResource theResource, BaseNarrativeDt<?> theNarrative) {
throw new UnsupportedOperationException();
public void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative) {
try {
theNarrative.setDivAsString("<div>help</div>");
} catch (Exception e) {
throw new Error(e);
}
theNarrative.setStatusAsString("generated");
}
@Override
public void generateNarrative(String theProfile, IBaseResource theResource, BaseNarrativeDt<?> theNarrative) throws DataFormatException {
theNarrative.getDiv().setValueAsString("<div>help</div>");
theNarrative.getStatus().setValueAsString("generated");
}
@Override
public String generateTitle(IBaseResource theResource) {
throw new UnsupportedOperationException();
}
@Override
public String generateTitle(String theProfile, IBaseResource theResource) {
throw new UnsupportedOperationException();
}
@Override
public void setFhirContext(FhirContext theFhirContext) {
// nothing
}
};
try {

View File

@ -298,7 +298,6 @@ public class SearchSearchServerDstu1Test {
Patient p = bundle.getResources(Patient.class).get(0);
assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString());
assertEquals("IDAAA (identifier123)", bundle.getEntries().get(0).getTitle().getValue());
}
@Test
@ -310,7 +309,6 @@ public class SearchSearchServerDstu1Test {
Patient p = bundle.getResources(Patient.class).get(0);
assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString());
assertEquals("IDAAA (identifier123)", bundle.getEntries().get(0).getTitle().getValue());
}
@Test
@ -364,7 +362,6 @@ public class SearchSearchServerDstu1Test {
Patient p = bundle.getResources(Patient.class).get(0);
assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString());
assertEquals("IDAAA (identifier123)", bundle.getEntries().get(0).getTitle().getValue());
}
/**

View File

@ -129,6 +129,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
for (BundleEntryComponent next : bundle.getEntry()) {
ValueSet nextValueSet = (ValueSet) next.getResource();
nextValueSet.getText().setDivAsString("");
String system = nextValueSet.getCodeSystem().getSystem();
if (isNotBlank(system)) {
theCodeSystems.put(system, nextValueSet);

View File

@ -36,4 +36,17 @@ public abstract class BaseNarrative extends Type implements INarrative {
protected abstract XhtmlNode getDiv();
public abstract Enumeration<?> getStatusElement();
@Override
public INarrative setStatusAsString(String theString) {
getStatusElement().setValueAsString(theString);
return this;
}
@Override
public String getStatusAsString() {
return getStatusElement().getValueAsString();
}
}

View File

@ -1527,7 +1527,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
List<WrapperElement> answers = new ArrayList<InstanceValidator.WrapperElement>();
element.getNamedChildren("answer", answers);
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId());
switch (qItem.getType()) {
case NULL:
case GROUP:
case DISPLAY:
break;
default:
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId());
}
if (answers.size() > 1)
rule(errors, IssueType.INVALID, answers.get(1).line(), answers.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response answer item with this linkId allowed");
@ -1564,7 +1571,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
validateQuestionnaireResponseItemType(errors, answer, ns, "string");
break;
case TEXT:
validateQuestionnaireResponseItemType(errors, answer, ns, "text");
validateQuestionnaireResponseItemType(errors, answer, ns, "string");
break;
case URL:
validateQuestionnaireResponseItemType(errors, answer, ns, "uri");

View File

@ -38,6 +38,7 @@ import org.hl7.fhir.dstu21.model.Medication;
import org.hl7.fhir.dstu21.model.MedicationOrder;
import org.hl7.fhir.dstu21.model.Observation;
import org.hl7.fhir.dstu21.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu21.model.Parameters;
import org.hl7.fhir.dstu21.model.Patient;
import org.hl7.fhir.dstu21.model.Quantity;
import org.hl7.fhir.dstu21.model.QuestionnaireResponse;
@ -45,11 +46,14 @@ import org.hl7.fhir.dstu21.model.Reference;
import org.hl7.fhir.dstu21.model.StringType;
import org.hl7.fhir.dstu21.model.UriType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.After;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.server.Constants;
import net.sf.json.JSON;
import net.sf.json.JSONSerializer;
@ -59,6 +63,39 @@ public class JsonParserDstu21Test {
private static final FhirContext ourCtx = FhirContext.forDstu2_1();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParserDstu21Test.class);
@Test
public void testEncodeParametersWithId() {
Parameters reqParms = new Parameters();
IdType patient = new IdType(1);
reqParms.addParameter().setName("patient").setValue(patient);
String enc = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(reqParms);
ourLog.info(enc);
assertThat(enc, containsString("\"valueId\":\"1\""));
}
@Test
public void testEncodeWithNarrative() {
Patient p = new Patient();
p.addName().addFamily("Smith").addGiven("John");
ourCtx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
String output = ourCtx.newJsonParser().encodeResourceToString(p);
ourLog.info(output);
assertThat(output, containsString("\"text\":{\"status\":\"generated\",\"div\":\"<div><div class=\\\"hapiHeaderText\\\"> John <b>SMITH </b></div>"));
}
@After
public void after() {
ourCtx.setNarrativeGenerator(null);
}
// FIXME: this should pass
@Test
@Ignore

View File

@ -32,7 +32,6 @@ import org.hamcrest.text.StringContainsInOrder;
import org.hl7.fhir.dstu21.model.Address.AddressUse;
import org.hl7.fhir.dstu21.model.AllergyIntolerance;
import org.hl7.fhir.dstu21.model.Annotation;
import org.hl7.fhir.dstu21.model.Attachment;
import org.hl7.fhir.dstu21.model.Binary;
import org.hl7.fhir.dstu21.model.Bundle;
import org.hl7.fhir.dstu21.model.Bundle.BundleEntryComponent;
@ -77,13 +76,14 @@ import org.hl7.fhir.dstu21.model.StringType;
import org.hl7.fhir.dstu21.model.UriType;
import org.hl7.fhir.dstu21.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.Constants;
@ -124,6 +124,24 @@ public class XmlParserDstu21Test {
}
@Test
public void testEncodeWithNarrative() {
Patient p = new Patient();
p.addName().addFamily("Smith").addGiven("John");
ourCtx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
String output = ourCtx.newXmlParser().encodeResourceToString(p);
ourLog.info(output);
assertThat(output, containsString("<text><status value=\"generated\"/><div xmlns=\"http://www.w3.org/1999/xhtml\"><div class=\"hapiHeaderText\"> John <b>SMITH </b>"));
}
@After
public void after() {
ourCtx.setNarrativeGenerator(null);
}
@Test
public void testEncodeEmptyBinary() {
String output = ourCtx.newXmlParser().encodeResourceToString(new Binary());
@ -541,7 +559,7 @@ public class XmlParserDstu21Test {
mo.addDosageInstruction().getTiming().getRepeat().setBounds(new Duration().setCode("code"));
String out = ourCtx.newXmlParser().encodeResourceToString(mo);
ourLog.info(out);
assertThat(out, containsString("</boundsDuration>"));
assertThat(out, containsString("</boundsQuantity>"));
mo = ourCtx.newXmlParser().parseResource(MedicationOrder.class, out);
Duration duration = (Duration) mo.getDosageInstruction().get(0).getTiming().getRepeat().getBounds();
@ -551,17 +569,15 @@ public class XmlParserDstu21Test {
/**
* See #216 - Profiled datatypes should use their unprofiled parent type as the choice[x] name
*/
@Test @Ignore
@Test
public void testEncodeAndParseProfiledDatatypeChoice() throws Exception {
IParser xmlParser = ourCtx.newXmlParser();
String input = IOUtils.toString(XmlParser.class.getResourceAsStream("/medicationstatement_invalidelement.xml"));
MedicationStatement ms = xmlParser.parseResource(MedicationStatement.class, input);
SimpleQuantity q = (SimpleQuantity) ms.getDosage().get(0).getQuantity();
assertEquals("1", q.getValueElement().getValueAsString());
MedicationStatement ms = new MedicationStatement();
ms.addDosage().setQuantity(new SimpleQuantity().setValue(123));
String output = xmlParser.encodeResourceToString(ms);
assertThat(output, containsString("<quantityQuantity><value value=\"1\"/></quantityQuantity>"));
assertThat(output, containsString("<quantityQuantity><value value=\"123\"/></quantityQuantity>"));
}
@Test
@ -1588,7 +1604,7 @@ public class XmlParserDstu21Test {
/**
* See #191
*/
@Test @Ignore
@Test
public void testParseBundleWithLinksOfUnknownRelation() throws Exception {
String input = IOUtils.toString(XmlParserDstu21Test.class.getResourceAsStream("/bundle_orion.xml"));
Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, input);

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@ -19,6 +21,7 @@ import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu21.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu21.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu21.model.Identifier.IdentifierUse;
import org.hl7.fhir.dstu21.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
@ -56,10 +59,10 @@ public class ResponseValidatingInterceptorDstu21Test {
}
myInterceptor = new ResponseValidatingInterceptor();
// myInterceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
// myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
// myInterceptor.setResponseHeaderName("X-RESP");
// myInterceptor.setResponseHeaderValue(RequestValidatingInterceptor.DEFAULT_RESPONSE_HEADER_VALUE);
// myInterceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
// myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
// myInterceptor.setResponseHeaderName("X-RESP");
// myInterceptor.setResponseHeaderValue(RequestValidatingInterceptor.DEFAULT_RESPONSE_HEADER_VALUE);
ourServlet.registerInterceptor(myInterceptor);
}
@ -91,7 +94,6 @@ public class ResponseValidatingInterceptorDstu21Test {
assertThat(responseContent, containsString("<severity value=\"error\"/>"));
}
@Test
public void testSearchJsonValidNoValidatorsSpecified() throws Exception {
Patient patient = new Patient();
@ -113,7 +115,6 @@ public class ResponseValidatingInterceptorDstu21Test {
assertThat(status.toString(), not(containsString("X-FHIR-Response-Validation")));
}
@Test
public void testSearchJsonValidNoValidatorsSpecifiedDefaultMessage() throws Exception {
myInterceptor.setResponseHeaderValueNoIssues("NO ISSUES");
@ -270,9 +271,54 @@ public class ResponseValidatingInterceptorDstu21Test {
ourLog.trace("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(status.toString(), (containsString("X-FHIR-Response-Validation: {\"resourceType\":\"OperationOutcome\",\"issue\":[{\"severity\":\"information\",\"code\":\"informational\",\"diagnostics\":\"No issues detected\"}]}")));
assertThat(status.toString(), (containsString(
"X-FHIR-Response-Validation: {\"resourceType\":\"OperationOutcome\",\"issue\":[{\"severity\":\"information\",\"code\":\"informational\",\"diagnostics\":\"No issues detected\"}]}")));
}
@Test
public void testLongHeaderTruncated() throws Exception {
IValidatorModule module = new FhirInstanceValidator();
myInterceptor.addValidatorModule(module);
myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
myInterceptor.setFailOnSeverity(null);
Patient patient = new Patient();
for (int i = 0; i < 1000; i++) {
patient.addContact().setGender(AdministrativeGender.MALE);
}
patient.setGender(AdministrativeGender.MALE);
myReturnResource = patient;
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient?foo=bar");
{
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.trace("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(status.getFirstHeader("X-FHIR-Response-Validation").getValue(), endsWith("..."));
assertThat(status.getFirstHeader("X-FHIR-Response-Validation").getValue(), startsWith("{\"resourceType\":\"OperationOutcome\""));
}
{
myInterceptor.setMaximumHeaderLength(100);
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.trace("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(status.getFirstHeader("X-FHIR-Response-Validation").getValue(), endsWith("..."));
assertThat(status.getFirstHeader("X-FHIR-Response-Validation").getValue(), startsWith("{\"resourceType\":\"OperationOutcome\""));
}
}
@AfterClass
public static void afterClass() throws Exception {
@ -309,7 +355,7 @@ public class ResponseValidatingInterceptorDstu21Test {
}
@Search
public ArrayList<IBaseResource> search(@OptionalParam(name="foo") StringParam theString) {
public ArrayList<IBaseResource> search(@OptionalParam(name = "foo") StringParam theString) {
ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>();
myReturnResource.setId("1");
retVal.add(myReturnResource);

View File

@ -37,6 +37,7 @@ import org.hl7.fhir.dstu21.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@ -170,7 +171,7 @@ public class FhirInstanceValidatorDstu21Test {
int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) {
ourLog.info("Result {}: {} - {} - {}", new Object[] { index, next.getSeverity(), next.getLocationString(), next.getMessage() });
ourLog.info("Result {}: {} - {}:{} {} - {}", new Object[] { index, next.getSeverity(), defaultString(next.getLocationLine()), defaultString(next.getLocationCol()), next.getLocationString(), next.getMessage() });
index++;
retVal.add(next);
@ -179,6 +180,10 @@ public class FhirInstanceValidatorDstu21Test {
return retVal;
}
private Object defaultString(Integer theLocationLine) {
return theLocationLine != null ? theLocationLine.toString() : "";
}
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
@ -262,7 +267,22 @@ public class FhirInstanceValidatorDstu21Test {
String input = IOUtils.toString(FhirInstanceValidatorDstu21Test.class.getResourceAsStream("/qr_jon.xml"));
ValidationResult output = myVal.validateWithResult(input);
assertEquals(output.toString(), 12, output.getMessages().size());
logResultsAndReturnAll(output);
assertEquals(output.toString(), 3, output.getMessages().size());
ourLog.info(output.getMessages().get(0).getLocationString());
ourLog.info(output.getMessages().get(0).getMessage());
}
@Test
@Ignore
public void testValidateStructureDefinition() throws IOException {
String input = IOUtils.toString(FhirInstanceValidatorDstu21Test.class.getResourceAsStream("/sdc-questionnaire.profile.xml"));
ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output);
assertEquals(output.toString(), 3, output.getMessages().size());
ourLog.info(output.getMessages().get(0).getLocationString());
ourLog.info(output.getMessages().get(0).getMessage());
}

View File

@ -0,0 +1,72 @@
<Bundle
xmlns="http://hl7.org/fhir">
<id value="487d3669-0e1c-4867-b124-400d1849548d"></id>
<type value="searchset"></type>
<base value="http://localhost:19080/fhir/dstu1"></base>
<link>
<relation value="just trying add link"></relation>
<url value="blarion"></url>
</link>
<link>
<relation value="self"></relation>
<url value="http://localhost:19080/fhir/dstu1/Observation?subject.identifier=puppet|CLONE-AA102"></url>
</link>
<entry>
<link>
<relation value="orionhealth.edit"></relation>
<url value="Observation"></url>
</link>
<resource>
<Observation
xmlns="http://hl7.org/fhir">
<id value="0d87f02c-da2c-4551-9ead-2956f0165a4f"></id>
<extension url="http://orionhealth.com/fhir/extensions#created-by"></extension>
<extension url="http://orionhealth.com/fhir/extensions#last-modified-by"></extension>
<extension url="http://orionhealth.com/fhir/extensions#received-instant">
<valueInstant value="2015-06-25T17:08:47.190+12:00"></valueInstant>
</extension>
<code>
<coding>
<system value="http://loinc.org"></system>
<code value="8867-4"></code>
</coding>
</code>
<valueString value="observationValue"></valueString>
<status value="final"></status>
<reliability value="ok"></reliability>
<subject>
<reference value="Patient/INGE6TSFFVAUCMJQGJAHA5LQOBSXI"></reference>
</subject>
</Observation>
</resource>
</entry>
<entry>
<link>
<relation value="orionhealth.edit"></relation>
<url value="Observation"></url>
</link>
<resource>
<Observation
xmlns="http://hl7.org/fhir">
<id value="c54ac0cc-a99f-40aa-9541-c5aa853a2e88"></id>
<extension url="http://orionhealth.com/fhir/extensions#created-by"></extension>
<extension url="http://orionhealth.com/fhir/extensions#last-modified-by"></extension>
<extension url="http://orionhealth.com/fhir/extensions#received-instant">
<valueInstant value="2015-06-25T17:08:47.190+12:00"></valueInstant>
</extension>
<code>
<coding>
<system value="http://loinc.org"></system>
<code value="3141-9"></code>
</coding>
</code>
<valueString value="observationValue"></valueString>
<status value="final"></status>
<reliability value="ok"></reliability>
<subject>
<reference value="Patient/INGE6TSFFVAUCMJQGJAHA5LQOBSXI"></reference>
</subject>
</Observation>
</resource>
</entry>
</Bundle>

View File

@ -29,7 +29,6 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -94,16 +93,6 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
}
if (myContext.getNarrativeGenerator() != null) {
String title = myContext.getNarrativeGenerator().generateTitle(next);
ourLog.trace("Narrative generator created title: {}", title);
if (StringUtils.isNotBlank(title)) {
ResourceMetadataKeyEnum.TITLE.put(next, title);
}
} else {
ourLog.trace("No narrative generator specified");
}
List<BaseResourceReferenceDt> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, BaseResourceReferenceDt.class);
do {
List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
@ -190,16 +179,6 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
}
if (myContext.getNarrativeGenerator() != null) {
String title = myContext.getNarrativeGenerator().generateTitle(next);
ourLog.trace("Narrative generator created title: {}", title);
if (StringUtils.isNotBlank(title)) {
ResourceMetadataKeyEnum.TITLE.put(next, title);
}
} else {
ourLog.trace("No narrative generator specified");
}
List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
do {
List<IResource> addedResourcesThisPass = new ArrayList<IResource>();

View File

@ -29,7 +29,7 @@ public class CustomThymeleafNarrativeGeneratorDstu2Test {
p.getName().addFamily("fam1").addGiven("given");
NarrativeDt narrative = new NarrativeDt();
gen.generateNarrative(p, narrative);
gen.generateNarrative(ourCtx, p, narrative);
String actual = narrative.getDiv().getValueAsString();
ourLog.info(actual);

View File

@ -62,23 +62,15 @@ public class DefaultThymeleafNarrativeGeneratorTestDstu2 {
value.setBirthDate(new Date(), TemporalPrecisionEnum.DAY);
NarrativeDt narrative = new NarrativeDt();
myGen.generateNarrative(value, narrative);
myGen.generateNarrative(ourCtx, value, narrative);
String output = narrative.getDiv().getValueAsString();
ourLog.info(output);
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> joe john <b>BLOW </b></div>"));
String title = myGen.generateTitle(value);
assertEquals("joe john BLOW (123456)", title);
// ourLog.info(title);
value.getIdentifierFirstRep().setValue("FOO MRN 123");
title = myGen.generateTitle(value);
assertEquals("joe john BLOW (FOO MRN 123)", title);
// ourLog.info(title);
}
@Test
@Ignore
public void testGenerateEncounter() throws DataFormatException {
Encounter enc = new Encounter();
@ -87,30 +79,13 @@ public class DefaultThymeleafNarrativeGeneratorTestDstu2 {
enc.setPeriod(new PeriodDt().setStart(new DateTimeDt("2001-01-02T11:11:00")));
enc.setType(ca.uhn.fhir.model.dstu2.valueset.EncounterTypeEnum.ANNUAL_DIABETES_MELLITUS_SCREENING);
String title = myGen.generateTitle(enc);
title = title.replaceAll("00 [A-Z0-9:+-]+ 2001", "00 TZ 2001"); // account for whatever time zone
assertEquals("1234567 / ADMS / ambulatory / Tue Jan 02 11:11:00 TZ 2001 - ?", title);
ourLog.info(title);
}
@Test
public void testGenerateDiagnosticReport() throws DataFormatException {
DiagnosticReport value = new DiagnosticReport();
value.getCode().setText("Some Diagnostic Report");
value.addResult().setReference("Observation/1");
value.addResult().setReference("Observation/2");
value.addResult().setReference("Observation/3");
NarrativeDt narrative = new NarrativeDt();
myGen.generateNarrative("http://hl7.org/fhir/profiles/DiagnosticReport", value, narrative);
String output = narrative.getDiv().getValueAsString();
myGen.generateNarrative(ourCtx, enc, narrative);
ourLog.info(output);
assertThat(output, StringContains.containsString(value.getCode().getTextElement().getValue()));
assertEquals("", narrative.getDivAsString());
}
@Test
public void testGenerateOperationOutcome() {
//@formatter:off
@ -133,17 +108,12 @@ public class DefaultThymeleafNarrativeGeneratorTestDstu2 {
// assertEquals("Operation Outcome (2 issues)", output);
NarrativeDt narrative = new NarrativeDt();
myGen.generateNarrative(null, oo, narrative);
myGen.generateNarrative(ourCtx, oo, narrative);
String output = narrative.getDiv().getValueAsString();
ourLog.info(output);
// oo = new OperationOutcome();
// oo.addIssue().setSeverity(IssueSeverityEnum.FATAL).setDetails("AA");
// output = gen.generateTitle(oo);
// ourLog.info(output);
// assertEquals("Operation Outcome (fatal)", output);
assertThat(output, containsString("<td><pre>YThis is a warning</pre></td>"));
}
@Test
@ -177,22 +147,17 @@ public class DefaultThymeleafNarrativeGeneratorTestDstu2 {
}
NarrativeDt narrative = new NarrativeDt();
myGen.generateNarrative("http://hl7.org/fhir/profiles/DiagnosticReport", value, narrative);
myGen.generateNarrative(ourCtx, value, narrative);
String output = narrative.getDiv().getValueAsString();
ourLog.info(output);
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> Some &amp; Diagnostic Report </div>"));
String title = myGen.generateTitle(value);
// ourLog.info(title);
assertEquals("Some & Diagnostic Report - final - 3 observations", title);
// Now try it with the parser
output = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(value);
ourLog.info(output);
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> Some &amp; Diagnostic Report </div>"));
}
@Test
@ -208,14 +173,11 @@ public class DefaultThymeleafNarrativeGeneratorTestDstu2 {
mp.setDateWritten(new DateTimeDt("2014-09-01"));
NarrativeDt narrative = new NarrativeDt();
myGen.generateNarrative(mp, narrative);
myGen.generateNarrative(ourCtx, mp, narrative);
assertTrue("Expected medication name of ciprofloaxin within narrative: " + narrative.getDiv().toString(), narrative.getDiv().toString().indexOf("ciprofloaxin") > -1);
assertTrue("Expected string status of ACTIVE within narrative: " + narrative.getDiv().toString(), narrative.getDiv().toString().indexOf("ACTIVE") > -1);
String title = myGen.generateTitle(mp);
assertEquals("ciprofloaxin", title);
}
@Test
@ -224,14 +186,11 @@ public class DefaultThymeleafNarrativeGeneratorTestDstu2 {
med.getCode().setText("ciproflaxin");
NarrativeDt narrative = new NarrativeDt();
myGen.generateNarrative(med, narrative);
myGen.generateNarrative(ourCtx, med, narrative);
String string = narrative.getDiv().toString();
assertThat(string, containsString("ciproflaxin"));
String title = myGen.generateTitle(med);
assertEquals("ciproflaxin", title);
}
}

View File

@ -110,18 +110,17 @@ public class XmlParserDstu2Test {
assertEquals(1, parsed.getUndeclaredExtensions().size());
ExtensionDt ext = parsed.getUndeclaredExtensions().get(0);
assertEquals("http://example.com", ext.getUrl());
assertEquals("THIS IS MARKDOWN", ((MarkdownDt)ext.getValue()).getValue());
assertEquals("THIS IS MARKDOWN", ((MarkdownDt) ext.getValue()).getValue());
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parsed);
assertThat(encoded, containsString("<valueMarkdown value=\"THIS IS MARKDOWN\"/>"));
}
@Test
public void testChoiceTypeWithProfiledType2() {
Parameters par = new Parameters();
par.addParameter().setValue((StringDt)new StringDt().setValue("ST"));
par.addParameter().setValue((MarkdownDt)new MarkdownDt().setValue("MD"));
par.addParameter().setValue((StringDt) new StringDt().setValue("ST"));
par.addParameter().setValue((MarkdownDt) new MarkdownDt().setValue("MD"));
String str = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(par);
ourLog.info(str);
@ -134,8 +133,6 @@ public class XmlParserDstu2Test {
assertEquals(MarkdownDt.class, par.getParameter().get(1).getValue().getClass());
}
@Test
public void testBundleWithBinary() {
//@formatter:off
@ -184,7 +181,6 @@ public class XmlParserDstu2Test {
ourCtx.newJsonParser().parseResource(encoded);
}
@Test
public void testEncodeDoesntIncludeUuidId() {
Patient p = new Patient();
@ -195,7 +191,6 @@ public class XmlParserDstu2Test {
assertThat(actual, not(containsString("78ef6f64c2f2")));
}
@Test
public void testContainedResourceInExtensionUndeclared() {
Patient p = new Patient();
@ -340,7 +335,6 @@ public class XmlParserDstu2Test {
}
@Test
public void testEncodeAndParseExtensions() throws Exception {
@ -566,7 +560,7 @@ public class XmlParserDstu2Test {
}
/**
* Test for #233 - This was reversed after a conversation with Grahame
* Test for #233
*/
@Test
public void testEncodeAndParseProfiledDatatype() {
@ -574,7 +568,7 @@ public class XmlParserDstu2Test {
mo.addDosageInstruction().getTiming().getRepeat().setBounds(new DurationDt().setCode("code"));
String out = ourCtx.newXmlParser().encodeResourceToString(mo);
ourLog.info(out);
assertThat(out, containsString("</boundsDuration>"));
assertThat(out, containsString("</boundsQuantity>"));
mo = ourCtx.newXmlParser().parseResource(MedicationOrder.class, out);
DurationDt duration = (DurationDt) mo.getDosageInstruction().get(0).getTiming().getRepeat().getBounds();
@ -587,7 +581,6 @@ public class XmlParserDstu2Test {
* Disabled because we reverted this change after a conversation with Grahame
*/
@Test
@Ignore
public void testEncodeAndParseProfiledDatatypeChoice() throws Exception {
IParser xmlParser = ourCtx.newXmlParser();
@ -597,7 +590,7 @@ public class XmlParserDstu2Test {
assertEquals("1", q.getValueElement().getValueAsString());
String output = xmlParser.encodeResourceToString(ms);
assertThat(output, containsString("<quantitySimpleQuantity><value value=\"1\"/></quantitySimpleQuantity>"));
assertThat(output, containsString("<quantityQuantity><value value=\"1\"/></quantityQuantity>"));
}
@Test

View File

@ -54,7 +54,6 @@ import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.BundleInclusionRule;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
@ -89,6 +88,22 @@ public class ResponseHighlightingInterceptorTest {
}
@Test
public void testGetInvalidResourceNoAcceptHeader() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Foobar/123");
CloseableHttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Resp: {}", responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(stringContainsInOrder("<span class='hlTagName'>OperationOutcome</span>", "Unknown resource type 'Foobar' - Server knows how to handle")));
assertThat(responseContent, (stringContainsInOrder("Unknown resource type 'Foobar'")));
assertThat(status.getFirstHeader("Content-Type").getValue(), containsString("application/xml+fhir"));
}
@Test
public void testGetRoot() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/");

View File

@ -1,12 +1,24 @@
package org.hl7.fhir.instance.model;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.Narrative.NarrativeStatus;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public abstract class BaseNarrative extends Type implements INarrative {
/**
@Override
public INarrative setStatusAsString(String theString) {
getStatusElement().setValueAsString(theString);
return this;
}
@Override
public String getStatusAsString() {
return getStatusElement().getValueAsString();
}
/**
* Sets the value of
*
* @param theString
@ -36,4 +48,6 @@ public abstract class BaseNarrative extends Type implements INarrative {
protected abstract XhtmlNode getDiv();
public abstract Enumeration<?> getStatusElement();
}

View File

@ -53,6 +53,7 @@ import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetCodeSystemComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.After;
@ -972,34 +973,19 @@ public class JsonParserHl7OrgTest {
Organization org = new Organization();
patient.getManagingOrganization().setResource(org);
INarrativeGenerator gen = new INarrativeGenerator() {
INarrativeGenerator gen = new INarrativeGenerator() {
@Override
public void generateNarrative(IBaseResource theResource, BaseNarrativeDt<?> theNarrative) {
throw new UnsupportedOperationException();
}
@Override
public void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative) {
try {
theNarrative.setDivAsString("<div>help</div>");
} catch (Exception e) {
throw new Error(e);
}
theNarrative.setStatusAsString("generated");
}
@Override
public void generateNarrative(String theProfile, IBaseResource theResource, BaseNarrativeDt<?> theNarrative) throws DataFormatException {
theNarrative.getDiv().setValueAsString("<div>help</div>");
theNarrative.getStatus().setValueAsString("generated");
}
@Override
public String generateTitle(IBaseResource theResource) {
throw new UnsupportedOperationException();
}
@Override
public String generateTitle(String theProfile, IBaseResource theResource) {
throw new UnsupportedOperationException();
}
@Override
public void setFhirContext(FhirContext theFhirContext) {
// nothing
}
};
};
FhirContext context = ourCtx;
context.setNarrativeGenerator(gen);

View File

@ -58,6 +58,7 @@ import org.hl7.fhir.instance.model.SimpleQuantity;
import org.hl7.fhir.instance.model.Specimen;
import org.hl7.fhir.instance.model.StringType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.junit.BeforeClass;
import org.junit.Ignore;
@ -1382,34 +1383,19 @@ public class XmlParserHl7OrgDstu2Test {
Organization org = new Organization();
patient.getManagingOrganization().setResource(org);
INarrativeGenerator gen = new INarrativeGenerator() {
INarrativeGenerator gen = new INarrativeGenerator() {
@Override
public void generateNarrative(IBaseResource theResource, BaseNarrativeDt<?> theNarrative) {
throw new UnsupportedOperationException();
}
@Override
public void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative) {
try {
theNarrative.setDivAsString("<div>help</div>");
} catch (Exception e) {
throw new Error(e);
}
theNarrative.setStatusAsString("generated");
}
@Override
public void generateNarrative(String theProfile, IBaseResource theResource, BaseNarrativeDt<?> theNarrative) throws DataFormatException {
theNarrative.getDiv().setValueAsString("<div>help</div>");
theNarrative.getStatus().setValueAsString("generated");
}
@Override
public String generateTitle(IBaseResource theResource) {
throw new UnsupportedOperationException();
}
@Override
public String generateTitle(String theProfile, IBaseResource theResource) {
throw new UnsupportedOperationException();
}
@Override
public void setFhirContext(FhirContext theFhirContext) {
// nothing
}
};
};
FhirContext context = ourCtx;
context.setNarrativeGenerator(gen);

View File

@ -600,21 +600,6 @@ public class BaseController {
}
}
/*
* DSTU2 no longer has a title in the bundle format, but it's still useful here..
*/
if (bundle != null) {
INarrativeGenerator gen = context.getNarrativeGenerator();
if (gen != null) {
for (BundleEntry next : bundle.getEntries()) {
if (next.getTitle().isEmpty() && next.getResource() != null) {
String title = gen.generateTitle(next.getResource());
next.getTitle().setValue(title);
}
}
}
}
resultDescription.append(" (").append(resultBody.length() + " bytes)");
Header[] requestHeaders = lastRequest != null ? applyHeaderFilters(lastRequest.getAllHeaders()) : new Header[0];

View File

@ -335,8 +335,8 @@
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
@ -556,7 +556,7 @@
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
<version>5.5.1.Final</version>
<version>5.5.2.Final</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
@ -941,7 +941,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>6.13</version>
<version>6.14.1</version>
</dependency>
</dependencies>
<configuration>

View File

@ -122,6 +122,23 @@
Add DSTU2.1 example to hpia-fhir-jpaserver-example. Thanks to Karl
Davis for the Pull Request!
</action>
<action type="add">
RestfulServer#setUseBrowserFriendlyContentTypes has been deprecated and its
functionality removed. The intention of this feature was that if it
detected a request coming in from a browser, it would serve up JSON/XML
using content types that caused the browsers to pretty print. But
each browser has different rules for when to pretty print, and
after we wrote that feature both Chrome and FF changed their rules to break it anyhow.
ResponseHighlightingInterceptor provides a better implementation of
this functionality and should be used instead.
</action>
<action type="remove">
Narrative generator framework has removed the
ability to generate resource titles. This
functionality was only useful for DSTU1
implementations and wasn't compatible
with coming changes to that API.
</action>
</release>
<release version="1.3" date="2015-11-14">
<action type="add">