Many bugfixes
This commit is contained in:
parent
79bdd94f72
commit
cb19b17e59
|
@ -33,6 +33,8 @@
|
||||||
|
|
||||||
* Support "Binary" resource, which is a special resource type
|
* Support "Binary" resource, which is a special resource type
|
||||||
|
|
||||||
|
* Submit "_pretty" as possible parameter to HL7
|
||||||
|
|
||||||
---------
|
---------
|
||||||
Issues:
|
Issues:
|
||||||
* Need to be able to bind Identifier.system to a set of allowable values in a profile. Graeme
|
* Need to be able to bind Identifier.system to a set of allowable values in a profile. Graeme
|
||||||
|
|
|
@ -8,7 +8,6 @@ import java.util.Set;
|
||||||
import ca.uhn.fhir.model.api.ICodeEnum;
|
import ca.uhn.fhir.model.api.ICodeEnum;
|
||||||
import ca.uhn.fhir.model.api.IDatatype;
|
import ca.uhn.fhir.model.api.IDatatype;
|
||||||
import ca.uhn.fhir.model.api.IElement;
|
import ca.uhn.fhir.model.api.IElement;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
|
||||||
import ca.uhn.fhir.model.api.annotation.Child;
|
import ca.uhn.fhir.model.api.annotation.Child;
|
||||||
import ca.uhn.fhir.model.api.annotation.Description;
|
import ca.uhn.fhir.model.api.annotation.Description;
|
||||||
|
|
||||||
|
@ -35,7 +34,8 @@ public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeDecl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IElement> theDatatype) {
|
public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IElement> theDatatype) {
|
||||||
if (myDatatype.equals(theDatatype)) {
|
Class<? extends IElement> datatype = theDatatype;
|
||||||
|
if (myDatatype.equals(datatype)) {
|
||||||
return myElementDefinition;
|
return myElementDefinition;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -74,4 +74,11 @@ public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeDecl
|
||||||
}
|
}
|
||||||
myCodeType = theType;
|
myCodeType = theType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "[" + getElementName() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,5 +13,4 @@ public class RuntimeChildCompositeDatatypeDefinition extends BaseRuntimeChildDat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,4 +83,9 @@ public class RuntimeChildResourceDefinition extends BaseRuntimeDeclaredChildDefi
|
||||||
|
|
||||||
myValidChildNames = Collections.unmodifiableSet(myValidChildNames);
|
myValidChildNames = Collections.unmodifiableSet(myValidChildNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "[" + getElementName() + "]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (T next : theValues) {
|
for (T next : theValues) {
|
||||||
getCoding().add(new BoundCodingDt<T>(myBinder, next));
|
getCoding().add(new CodingDt(myBinder.toSystemString(next), myBinder.toCodeString(next)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,16 @@ import ca.uhn.fhir.model.api.IValueSetEnumBinder;
|
||||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||||
import ca.uhn.fhir.model.dstu.composite.CodingDt;
|
import ca.uhn.fhir.model.dstu.composite.CodingDt;
|
||||||
|
|
||||||
@DatatypeDef(name = "Coding")
|
@DatatypeDef(name = "Coding", isSpecialization=true)
|
||||||
public class BoundCodingDt<T extends Enum<?>> extends CodingDt {
|
public class BoundCodingDt__<T extends Enum<?>> extends CodingDt {
|
||||||
|
|
||||||
private IValueSetEnumBinder<T> myBinder;
|
private IValueSetEnumBinder<T> myBinder;
|
||||||
|
|
||||||
public BoundCodingDt(IValueSetEnumBinder<T> theBinder) {
|
public BoundCodingDt__(IValueSetEnumBinder<T> theBinder) {
|
||||||
myBinder = theBinder;
|
myBinder = theBinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoundCodingDt(IValueSetEnumBinder<T> theBinder, T theValue) {
|
public BoundCodingDt__(IValueSetEnumBinder<T> theBinder, T theValue) {
|
||||||
myBinder = theBinder;
|
myBinder = theBinder;
|
||||||
setValueAsEnum(theValue);
|
setValueAsEnum(theValue);
|
||||||
}
|
}
|
|
@ -8,9 +8,7 @@ import ca.uhn.fhir.model.api.annotation.SimpleSetter;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the FHIR ID type. This is the actual resource ID, meaning the ID that
|
* Represents the FHIR ID type. This is the actual resource ID, meaning the ID that will be used in RESTful URLs, Resource References, etc. to represent a specific instance of a resource.
|
||||||
* will be used in RESTful URLs, Resource References, etc. to represent a specific
|
|
||||||
* instance of a resource.
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
|
* <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
|
||||||
|
@ -32,6 +30,17 @@ public class IdDt extends BasePrimitive<String> {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ID, using a BigDecimal input. Uses {@link BigDecimal#toPlainString()} to generate the string representation.
|
||||||
|
*/
|
||||||
|
public IdDt(BigDecimal thePid) {
|
||||||
|
if (thePid != null) {
|
||||||
|
setValue(thePid.toPlainString());
|
||||||
|
} else {
|
||||||
|
setValue(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new ID using a long
|
* Create a new ID using a long
|
||||||
*/
|
*/
|
||||||
|
@ -56,14 +65,24 @@ public class IdDt extends BasePrimitive<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new ID, using a BigDecimal input. Uses {@link BigDecimal#toPlainString()} to generate the string representation.
|
* Returns the value of this ID as a big decimal, or <code>null</code> if the value is null
|
||||||
|
*
|
||||||
|
* @throws NumberFormatException
|
||||||
|
* If the value is not a valid BigDecimal
|
||||||
*/
|
*/
|
||||||
public IdDt(BigDecimal thePid) {
|
public BigDecimal asBigDecimal() {
|
||||||
if (thePid != null) {
|
if (getValue() == null) {
|
||||||
setValue(thePid.toPlainString());
|
return null;
|
||||||
} else {
|
|
||||||
setValue(null);
|
|
||||||
}
|
}
|
||||||
|
return new BigDecimal(getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to <code>this</code> IdDt. It is generally not neccesary to use this method but it is provided for consistency with the rest of the API.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public IdDt getId() {
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -76,6 +95,14 @@ public class IdDt extends BasePrimitive<String> {
|
||||||
return myValue;
|
return myValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the value from the given IdDt to <code>this</code> IdDt. It is generally not neccesary to use this method but it is provided for consistency with the rest of the API.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setId(IdDt theId) {
|
||||||
|
setValue(theId.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the value
|
* Set the value
|
||||||
*
|
*
|
||||||
|
@ -114,16 +141,4 @@ public class IdDt extends BasePrimitive<String> {
|
||||||
return myValue;
|
return myValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of this ID as a big decimal, or <code>null</code> if the value is null
|
|
||||||
*
|
|
||||||
* @throws NumberFormatException If the value is not a valid BigDecimal
|
|
||||||
*/
|
|
||||||
public BigDecimal asBigDecimal() {
|
|
||||||
if (getValue() == null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new BigDecimal(getValueAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,5 +101,13 @@ public class StringDt extends BasePrimitive<String> implements IQueryParameterTy
|
||||||
return getValue();
|
return getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <code>true</code> if this datatype has no extensions, and has either a <code>null</code>
|
||||||
|
* value or an empty ("") value.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return super.isEmpty() && StringUtils.isBlank(getValue());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ public class JsonParser extends BaseParser implements IParser {
|
||||||
writeTagWithTextNode(eventWriter, "id", nextEntry.getEntryId());
|
writeTagWithTextNode(eventWriter, "id", nextEntry.getEntryId());
|
||||||
|
|
||||||
eventWriter.writeStartArray("link");
|
eventWriter.writeStartArray("link");
|
||||||
writeAtomLink(eventWriter, "self", theBundle.getLinkSelf());
|
writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf());
|
||||||
eventWriter.writeEnd();
|
eventWriter.writeEnd();
|
||||||
|
|
||||||
writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());
|
writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());
|
||||||
|
|
|
@ -100,7 +100,7 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase());
|
writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase());
|
||||||
|
|
||||||
if (theBundle.getTotalResults().getValue() != null) {
|
if (theBundle.getTotalResults().getValue() != null) {
|
||||||
eventWriter.writeStartElement("os", "totalResults",OPENSEARCH_NS);
|
eventWriter.writeStartElement("os", "totalResults", OPENSEARCH_NS);
|
||||||
eventWriter.writeNamespace("os", OPENSEARCH_NS);
|
eventWriter.writeNamespace("os", OPENSEARCH_NS);
|
||||||
eventWriter.writeCharacters(theBundle.getTotalResults().getValue().toString());
|
eventWriter.writeCharacters(theBundle.getTotalResults().getValue().toString());
|
||||||
eventWriter.writeEndElement();
|
eventWriter.writeEndElement();
|
||||||
|
@ -119,6 +119,13 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
for (BundleEntry nextEntry : theBundle.getEntries()) {
|
for (BundleEntry nextEntry : theBundle.getEntries()) {
|
||||||
eventWriter.writeStartElement("entry");
|
eventWriter.writeStartElement("entry");
|
||||||
|
|
||||||
|
writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
|
||||||
|
writeTagWithTextNode(eventWriter, "id", nextEntry.getEntryId());
|
||||||
|
|
||||||
|
if (!nextEntry.getLinkSelf().isEmpty()) {
|
||||||
|
writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf());
|
||||||
|
}
|
||||||
|
|
||||||
eventWriter.writeStartElement("content");
|
eventWriter.writeStartElement("content");
|
||||||
eventWriter.writeAttribute("type", "text/xml");
|
eventWriter.writeAttribute("type", "text/xml");
|
||||||
|
|
||||||
|
@ -138,7 +145,7 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String encodeResourceToString(IResource theResource) throws DataFormatException {
|
public String encodeResourceToString(IResource theResource) throws DataFormatException {
|
||||||
if (theResource==null) {
|
if (theResource == null) {
|
||||||
throw new NullPointerException("Resource can not be null");
|
throw new NullPointerException("Resource can not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,8 +207,6 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
return parseResource(theResourceType, streamReader);
|
return parseResource(theResourceType, streamReader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IParser setPrettyPrint(boolean thePrettyPrint) {
|
public IParser setPrettyPrint(boolean thePrettyPrint) {
|
||||||
myPrettyPrint = thePrettyPrint;
|
myPrettyPrint = thePrettyPrint;
|
||||||
|
@ -290,7 +295,8 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encodeChildElementToStreamWriter(XMLStreamWriter theEventWriter, IElement nextValue, String childName, BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl) throws XMLStreamException, DataFormatException {
|
private void encodeChildElementToStreamWriter(XMLStreamWriter theEventWriter, IElement nextValue, String childName, BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl)
|
||||||
|
throws XMLStreamException, DataFormatException {
|
||||||
if (nextValue.isEmpty()) {
|
if (nextValue.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -355,7 +361,8 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encodeCompositeElementChildrenToStreamWriter(IElement theElement, XMLStreamWriter theEventWriter, List<? extends BaseRuntimeChildDefinition> children) throws XMLStreamException, DataFormatException {
|
private void encodeCompositeElementChildrenToStreamWriter(IElement theElement, XMLStreamWriter theEventWriter, List<? extends BaseRuntimeChildDefinition> children) throws XMLStreamException,
|
||||||
|
DataFormatException {
|
||||||
for (BaseRuntimeChildDefinition nextChild : children) {
|
for (BaseRuntimeChildDefinition nextChild : children) {
|
||||||
List<? extends IElement> values = nextChild.getAccessor().getValues(theElement);
|
List<? extends IElement> values = nextChild.getAccessor().getValues(theElement);
|
||||||
if (values == null || values.isEmpty()) {
|
if (values == null || values.isEmpty()) {
|
||||||
|
@ -392,7 +399,8 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encodeCompositeElementToStreamWriter(IElement theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition<?> resDef) throws XMLStreamException, DataFormatException {
|
private void encodeCompositeElementToStreamWriter(IElement theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition<?> resDef) throws XMLStreamException,
|
||||||
|
DataFormatException {
|
||||||
encodeExtensionsIfPresent(theEventWriter, theElement);
|
encodeExtensionsIfPresent(theEventWriter, theElement);
|
||||||
encodeCompositeElementChildrenToStreamWriter(theElement, theEventWriter, resDef.getExtensions());
|
encodeCompositeElementChildrenToStreamWriter(theElement, theEventWriter, resDef.getExtensions());
|
||||||
encodeCompositeElementChildrenToStreamWriter(theElement, theEventWriter, resDef.getChildren());
|
encodeCompositeElementChildrenToStreamWriter(theElement, theEventWriter, resDef.getChildren());
|
||||||
|
@ -419,7 +427,6 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludeResourceId) throws XMLStreamException, DataFormatException {
|
private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludeResourceId) throws XMLStreamException, DataFormatException {
|
||||||
super.containResourcesForEncoding(theResource);
|
super.containResourcesForEncoding(theResource);
|
||||||
|
|
||||||
|
@ -449,7 +456,7 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
IElement nextValue = next.getValue();
|
IElement nextValue = next.getValue();
|
||||||
RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
|
RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
|
||||||
String childName = extDef.getChildNameByDatatype(nextValue.getClass());
|
String childName = extDef.getChildNameByDatatype(nextValue.getClass());
|
||||||
if (childName==null) {
|
if (childName == null) {
|
||||||
throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + nextValue.getClass().getCanonicalName());
|
throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + nextValue.getClass().getCanonicalName());
|
||||||
}
|
}
|
||||||
BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(nextValue.getClass());
|
BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(nextValue.getClass());
|
||||||
|
@ -579,7 +586,6 @@ public class XmlParser extends BaseParser implements IParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theStringDt) throws XMLStreamException {
|
private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theStringDt) throws XMLStreamException {
|
||||||
theEventWriter.writeStartElement(theElementName);
|
theEventWriter.writeStartElement(theElementName);
|
||||||
if (StringUtils.isNotBlank(theStringDt.getValue())) {
|
if (StringUtils.isNotBlank(theStringDt.getValue())) {
|
||||||
|
|
|
@ -10,11 +10,23 @@ import java.lang.annotation.Target;
|
||||||
* be populated with the "_include" values for a search param.
|
* be populated with the "_include" values for a search param.
|
||||||
* <p>
|
* <p>
|
||||||
* Only one parameter may be annotated with this annotation, and that
|
* Only one parameter may be annotated with this annotation, and that
|
||||||
* parameter should be of type Collection, List, or Set.
|
* parameter should be one of the following:
|
||||||
* </p>
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li><code>Collection<PathSpecification></code></li>
|
||||||
|
* <li><code>List<PathSpecification></code></li>
|
||||||
|
* <li><code>Set<PathSpecification></code></li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(value= {ElementType.PARAMETER})
|
@Target(value= {ElementType.PARAMETER})
|
||||||
public @interface Include {
|
public @interface Include {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional parameter, if provided the server will only allow the values
|
||||||
|
* within the given set. If an _include parameter is passed to the server
|
||||||
|
* which does not match any allowed values the server will return an error.
|
||||||
|
*/
|
||||||
|
String[] allow() default {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,9 @@ public class SearchMethodBinding extends BaseMethodBinding {
|
||||||
IParameter param = myParameters.get(i);
|
IParameter param = myParameters.get(i);
|
||||||
String[] value = parameterValues.get(param.getName());
|
String[] value = parameterValues.get(param.getName());
|
||||||
if (value == null || value.length == 0) {
|
if (value == null || value.length == 0) {
|
||||||
|
if (param.handlesMissing()) {
|
||||||
|
params[i] = param.parse(new ArrayList<List<String>>(0));
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,20 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
|
||||||
public interface IParameter {
|
public interface IParameter {
|
||||||
|
|
||||||
public abstract List<List<String>> encode(Object theObject) throws InternalErrorException;
|
List<List<String>> encode(Object theObject) throws InternalErrorException;
|
||||||
|
|
||||||
public abstract String getName();
|
String getName();
|
||||||
|
|
||||||
public abstract Object parse(List<List<String>> theString) throws InternalErrorException, InvalidRequestException;
|
Object parse(List<List<String>> theString) throws InternalErrorException, InvalidRequestException;
|
||||||
|
|
||||||
public abstract boolean isRequired();
|
boolean isRequired();
|
||||||
|
|
||||||
public abstract SearchParamTypeEnum getParamType();
|
/**
|
||||||
|
* Parameter should return true if {@link #parse(List)} should be called even
|
||||||
|
* if the query string contained no values for the given parameter
|
||||||
|
*/
|
||||||
|
boolean handlesMissing();
|
||||||
|
|
||||||
|
SearchParamTypeEnum getParamType();
|
||||||
|
|
||||||
}
|
}
|
|
@ -3,19 +3,29 @@ package ca.uhn.fhir.rest.param;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.PathSpecification;
|
import ca.uhn.fhir.model.api.PathSpecification;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
|
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Include;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
|
||||||
public class IncludeParameter implements IParameter {
|
public class IncludeParameter implements IParameter {
|
||||||
|
|
||||||
private Class<? extends Collection<PathSpecification>> myInstantiableCollectionType;
|
private Class<? extends Collection<PathSpecification>> myInstantiableCollectionType;
|
||||||
|
private HashSet<String> myAllow;
|
||||||
|
|
||||||
public IncludeParameter(Class<? extends Collection<PathSpecification>> theInstantiableCollectionType) {
|
public IncludeParameter(Include theAnnotation, Class<? extends Collection<PathSpecification>> theInstantiableCollectionType) {
|
||||||
myInstantiableCollectionType = theInstantiableCollectionType;
|
myInstantiableCollectionType = theInstantiableCollectionType;
|
||||||
|
if (theAnnotation.allow().length > 0) {
|
||||||
|
myAllow = new HashSet<String>();
|
||||||
|
for (String next : theAnnotation.allow()) {
|
||||||
|
myAllow.add(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -53,7 +63,13 @@ public class IncludeParameter implements IParameter {
|
||||||
throw new InvalidRequestException("'OR' query parameters (values containing ',') are not supported in _include parameters");
|
throw new InvalidRequestException("'OR' query parameters (values containing ',') are not supported in _include parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
retVal.add(new PathSpecification(nextParamList.get(0)));
|
String value = nextParamList.get(0);
|
||||||
|
if (myAllow != null) {
|
||||||
|
if (!myAllow.contains(value)) {
|
||||||
|
throw new InvalidRequestException("Invalid _include parameter value: '" + value + "'. Valid values are: " + new TreeSet<String>(myAllow));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retVal.add(new PathSpecification(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
|
@ -69,4 +85,9 @@ public class IncludeParameter implements IParameter {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handlesMissing() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,8 @@ public class SearchParameter implements IParameter {
|
||||||
throw new ConfigurationException("Unknown search parameter type: " + type);
|
throw new ConfigurationException("Unknown search parameter type: " + type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NB: Once this is enabled, we should return true from handlesMissing if
|
||||||
|
// it's a collection type
|
||||||
// if (theInnerCollectionType != null) {
|
// if (theInnerCollectionType != null) {
|
||||||
// this.parser = new CollectionBinder(this.parser, theInnerCollectionType);
|
// this.parser = new CollectionBinder(this.parser, theInnerCollectionType);
|
||||||
// }
|
// }
|
||||||
|
@ -115,4 +117,9 @@ public class SearchParameter implements IParameter {
|
||||||
return myParamType;
|
return myParamType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handlesMissing() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ public class Constants {
|
||||||
public static final Set<String> FORMAT_VAL_XML;
|
public static final Set<String> FORMAT_VAL_XML;
|
||||||
public static final Set<String> FORMAT_VAL_JSON;
|
public static final Set<String> FORMAT_VAL_JSON;
|
||||||
public static final Map<String, EncodingUtil> FORMAT_VAL_TO_ENCODING;
|
public static final Map<String, EncodingUtil> FORMAT_VAL_TO_ENCODING;
|
||||||
|
public static final String CT_XML = "application/xml";
|
||||||
|
public static final String CT_JSON = "application/json";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Map<String, EncodingUtil> valToEncoding = new HashMap<String, EncodingUtil>();
|
Map<String, EncodingUtil> valToEncoding = new HashMap<String, EncodingUtil>();
|
||||||
|
|
|
@ -2,16 +2,20 @@ package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
public enum EncodingUtil {
|
public enum EncodingUtil {
|
||||||
|
|
||||||
XML(Constants.CT_FHIR_XML, Constants.CT_ATOM_XML),
|
XML(Constants.CT_FHIR_XML, Constants.CT_ATOM_XML, Constants.CT_XML),
|
||||||
|
|
||||||
JSON(Constants.CT_FHIR_JSON, Constants.CT_FHIR_JSON);
|
JSON(Constants.CT_FHIR_JSON, Constants.CT_FHIR_JSON, Constants.CT_JSON)
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
private String myResourceContentType;
|
private String myResourceContentType;
|
||||||
private String myBundleContentType;
|
private String myBundleContentType;
|
||||||
|
private String myBrowserFriendlyContentType;
|
||||||
|
|
||||||
EncodingUtil(String theResourceContentType, String theBundleContentType) {
|
EncodingUtil(String theResourceContentType, String theBundleContentType, String theBrowserFriendlyContentType) {
|
||||||
myResourceContentType = theResourceContentType;
|
myResourceContentType = theResourceContentType;
|
||||||
myBundleContentType = theBundleContentType;
|
myBundleContentType = theBundleContentType;
|
||||||
|
myBrowserFriendlyContentType = theBrowserFriendlyContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBundleContentType() {
|
public String getBundleContentType() {
|
||||||
|
@ -22,4 +26,8 @@ public enum EncodingUtil {
|
||||||
return myResourceContentType;
|
return myResourceContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBrowserFriendlyBundleContentType() {
|
||||||
|
return myBrowserFriendlyContentType;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,24 @@ import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
|
||||||
|
|
||||||
public abstract class RestfulServer extends HttpServlet {
|
public abstract class RestfulServer extends HttpServlet {
|
||||||
|
|
||||||
|
private static final String PARAM_HISTORY = "_history";
|
||||||
|
private static final String PARAM_PRETTY = "_pretty";
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private FhirContext myFhirContext;
|
private FhirContext myFhirContext;
|
||||||
|
private boolean myUseBrowserFriendlyContentTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
|
||||||
|
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<Class<? extends IResource>, IResourceProvider> myTypeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
|
private Map<Class<? extends IResource>, IResourceProvider> myTypeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
|
||||||
|
|
||||||
|
@ -81,7 +94,6 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
|
handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
handleRequest(SearchMethodBinding.RequestType.GET, request, response);
|
handleRequest(SearchMethodBinding.RequestType.GET, request, response);
|
||||||
|
@ -102,7 +114,6 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
handleRequest(SearchMethodBinding.RequestType.PUT, request, response);
|
handleRequest(SearchMethodBinding.RequestType.PUT, request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void findResourceMethods(IResourceProvider theProvider) throws Exception {
|
private void findResourceMethods(IResourceProvider theProvider) throws Exception {
|
||||||
|
|
||||||
Class<? extends IResource> resourceType = theProvider.getResourceType();
|
Class<? extends IResource> resourceType = theProvider.getResourceType();
|
||||||
|
@ -131,12 +142,11 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public FhirContext getFhirContext() {
|
public FhirContext getFhirContext() {
|
||||||
return myFhirContext;
|
return myFhirContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IParser getNewParser(EncodingUtil theResponseEncoding) {
|
private IParser getNewParser(EncodingUtil theResponseEncoding, boolean thePrettyPrint) {
|
||||||
IParser parser;
|
IParser parser;
|
||||||
switch (theResponseEncoding) {
|
switch (theResponseEncoding) {
|
||||||
case JSON:
|
case JSON:
|
||||||
|
@ -147,7 +157,7 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
parser = myFhirContext.newXmlParser();
|
parser = myFhirContext.newXmlParser();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return parser;
|
return parser.setPrettyPrint(thePrettyPrint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<ResourceBinding> getResourceBindings() {
|
public Collection<ResourceBinding> getResourceBindings() {
|
||||||
|
@ -160,8 +170,7 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
public abstract Collection<IResourceProvider> getResourceProviders();
|
public abstract Collection<IResourceProvider> getResourceProviders();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should be overridden to provide a security manager instance.
|
* This method should be overridden to provide a security manager instance. By default, returns null.
|
||||||
* By default, returns null.
|
|
||||||
*/
|
*/
|
||||||
public ISecurityManager getSecurityManager() {
|
public ISecurityManager getSecurityManager() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -182,9 +191,16 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
securityManager.authenticate(request);
|
securityManager.authenticate(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String uaHeader = request.getHeader("user-agent");
|
||||||
|
boolean requestIsBrowser = false;
|
||||||
|
if (uaHeader != null && uaHeader.contains("Mozilla")) {
|
||||||
|
requestIsBrowser = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
String resourceName = null;
|
String resourceName = null;
|
||||||
String requestFullPath = StringUtils.defaultString(request.getRequestURI());
|
String requestFullPath = StringUtils.defaultString(request.getRequestURI());
|
||||||
// String contextPath = StringUtils.defaultString(request.getContextPath());
|
// String contextPath = StringUtils.defaultString(request.getContextPath());
|
||||||
String servletPath = StringUtils.defaultString(request.getServletPath());
|
String servletPath = StringUtils.defaultString(request.getServletPath());
|
||||||
StringBuffer requestUrl = request.getRequestURL();
|
StringBuffer requestUrl = request.getRequestURL();
|
||||||
String servletContextPath = "";
|
String servletContextPath = "";
|
||||||
|
@ -208,13 +224,27 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
requestPath = requestPath.substring(1);
|
requestPath = requestPath.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int contextIndex = requestUrl.indexOf(servletPath);
|
int contextIndex;
|
||||||
|
if (servletPath.length()==0) {
|
||||||
|
contextIndex = requestUrl.indexOf(requestPath);
|
||||||
|
}else {
|
||||||
|
contextIndex = requestUrl.indexOf(servletPath);
|
||||||
|
}
|
||||||
|
|
||||||
String fhirServerBase = requestUrl.substring(0, contextIndex + servletPath.length());
|
String fhirServerBase = requestUrl.substring(0, contextIndex + servletPath.length());
|
||||||
String completeUrl = StringUtils.isNotBlank(request.getQueryString()) ? requestUrl + "?" + request.getQueryString() : requestUrl.toString();
|
String completeUrl = StringUtils.isNotBlank(request.getQueryString()) ? requestUrl + "?" + request.getQueryString() : requestUrl.toString();
|
||||||
|
|
||||||
Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());
|
Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());
|
||||||
EncodingUtil responseEncoding = determineResponseEncoding(request, params);
|
EncodingUtil responseEncoding = determineResponseEncoding(request, params);
|
||||||
|
|
||||||
|
String[] pretty = params.remove(PARAM_PRETTY);
|
||||||
|
boolean prettyPrint = false;
|
||||||
|
if (pretty != null && pretty.length > 0) {
|
||||||
|
if ("true".equals(pretty[0])) {
|
||||||
|
prettyPrint = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StringTokenizer tok = new StringTokenizer(requestPath, "/");
|
StringTokenizer tok = new StringTokenizer(requestPath, "/");
|
||||||
if (!tok.hasMoreTokens()) {
|
if (!tok.hasMoreTokens()) {
|
||||||
throw new MethodNotFoundException("No resource name specified");
|
throw new MethodNotFoundException("No resource name specified");
|
||||||
|
@ -244,7 +274,7 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
|
|
||||||
if (tok.hasMoreTokens()) {
|
if (tok.hasMoreTokens()) {
|
||||||
String nextString = tok.nextToken();
|
String nextString = tok.nextToken();
|
||||||
if (nextString.startsWith("_history")) {
|
if (nextString.startsWith(PARAM_HISTORY)) {
|
||||||
if (tok.hasMoreTokens()) {
|
if (tok.hasMoreTokens()) {
|
||||||
versionId = new IdDt(tok.nextToken());
|
versionId = new IdDt(tok.nextToken());
|
||||||
} else {
|
} else {
|
||||||
|
@ -271,7 +301,7 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
List<IResource> result = resourceMethod.invokeServer(resourceBinding.getResourceProvider(), id, versionId, params);
|
List<IResource> result = resourceMethod.invokeServer(resourceBinding.getResourceProvider(), id, versionId, params);
|
||||||
switch (resourceMethod.getReturnType()) {
|
switch (resourceMethod.getReturnType()) {
|
||||||
case BUNDLE:
|
case BUNDLE:
|
||||||
streamResponseAsBundle(response, result, responseEncoding, fhirServerBase, completeUrl);
|
streamResponseAsBundle(response, result, responseEncoding, fhirServerBase, completeUrl, prettyPrint, requestIsBrowser);
|
||||||
break;
|
break;
|
||||||
case RESOURCE:
|
case RESOURCE:
|
||||||
if (result.size() == 0) {
|
if (result.size() == 0) {
|
||||||
|
@ -279,7 +309,7 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
} else if (result.size() > 1) {
|
} else if (result.size() > 1) {
|
||||||
throw new InternalErrorException("Method returned multiple resources");
|
throw new InternalErrorException("Method returned multiple resources");
|
||||||
}
|
}
|
||||||
streamResponseAsResource(response, result.get(0), resourceBinding, responseEncoding);
|
streamResponseAsResource(response, result.get(0), responseEncoding, prettyPrint, requestIsBrowser);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// resourceMethod.get
|
// resourceMethod.get
|
||||||
|
@ -344,16 +374,25 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void streamResponseAsBundle(HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingUtil theResponseEncoding, String theFhirServerBase, String theCompleteUrl) throws IOException {
|
private void streamResponseAsBundle(HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingUtil theResponseEncoding, String theServerBase, String theCompleteUrl,
|
||||||
|
boolean thePrettyPrint, boolean theRequestIsBrowser) throws IOException {
|
||||||
|
assert theServerBase.endsWith("/");
|
||||||
|
|
||||||
theHttpResponse.setStatus(200);
|
theHttpResponse.setStatus(200);
|
||||||
theHttpResponse.setContentType(theResponseEncoding.getBundleContentType());
|
|
||||||
|
if (theRequestIsBrowser && myUseBrowserFriendlyContentTypes) {
|
||||||
|
theHttpResponse.setContentType(theResponseEncoding.getBrowserFriendlyBundleContentType());
|
||||||
|
} else {
|
||||||
|
theHttpResponse.setContentType(theResponseEncoding.getBundleContentType());
|
||||||
|
}
|
||||||
|
|
||||||
theHttpResponse.setCharacterEncoding("UTF-8");
|
theHttpResponse.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.getAuthorName().setValue(getClass().getCanonicalName());
|
bundle.getAuthorName().setValue(getClass().getCanonicalName());
|
||||||
bundle.getBundleId().setValue(UUID.randomUUID().toString());
|
bundle.getBundleId().setValue(UUID.randomUUID().toString());
|
||||||
bundle.getPublished().setToCurrentTimeInLocalTimeZone();
|
bundle.getPublished().setToCurrentTimeInLocalTimeZone();
|
||||||
bundle.getLinkBase().setValue(theFhirServerBase);
|
bundle.getLinkBase().setValue(theServerBase);
|
||||||
bundle.getLinkSelf().setValue(theCompleteUrl);
|
bundle.getLinkSelf().setValue(theCompleteUrl);
|
||||||
|
|
||||||
for (IResource next : theResult) {
|
for (IResource next : theResult) {
|
||||||
|
@ -361,23 +400,55 @@ public abstract class RestfulServer extends HttpServlet {
|
||||||
bundle.getEntries().add(entry);
|
bundle.getEntries().add(entry);
|
||||||
|
|
||||||
entry.setResource(next);
|
entry.setResource(next);
|
||||||
|
|
||||||
|
RuntimeResourceDefinition def = myFhirContext.getResourceDefinition(next);
|
||||||
|
|
||||||
|
if (next.getId() != null && StringUtils.isNotBlank(next.getId().getValue())) {
|
||||||
|
entry.getEntryId().setValue(next.getId().getValue());
|
||||||
|
entry.getTitle().setValue(def.getName() + " " + next.getId().getValue());
|
||||||
|
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
b.append(theServerBase);
|
||||||
|
b.append(def.getName());
|
||||||
|
b.append('/');
|
||||||
|
b.append(next.getId().getValue());
|
||||||
|
boolean haveQ = false;
|
||||||
|
if (thePrettyPrint) {
|
||||||
|
b.append('?').append(PARAM_PRETTY).append("=true");
|
||||||
|
haveQ = true;
|
||||||
|
}
|
||||||
|
if (theResponseEncoding == EncodingUtil.JSON) {
|
||||||
|
if (!haveQ) {
|
||||||
|
b.append('?');
|
||||||
|
haveQ = true;
|
||||||
|
} else {
|
||||||
|
b.append('&');
|
||||||
|
}
|
||||||
|
b.append(Constants.PARAM_FORMAT).append("=json");
|
||||||
|
}
|
||||||
|
entry.getLinkSelf().setValue(b.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle.getTotalResults().setValue(theResult.size());
|
bundle.getTotalResults().setValue(theResult.size());
|
||||||
|
|
||||||
PrintWriter writer = theHttpResponse.getWriter();
|
PrintWriter writer = theHttpResponse.getWriter();
|
||||||
getNewParser(theResponseEncoding).encodeBundleToWriter(bundle, writer);
|
getNewParser(theResponseEncoding, thePrettyPrint).encodeBundleToWriter(bundle, writer);
|
||||||
writer.close();
|
writer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void streamResponseAsResource(HttpServletResponse theHttpResponse, IResource theResource, ResourceBinding theResourceBinding, EncodingUtil theResponseEncoding) throws IOException {
|
private void streamResponseAsResource(HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser) throws IOException {
|
||||||
|
|
||||||
theHttpResponse.setStatus(200);
|
theHttpResponse.setStatus(200);
|
||||||
theHttpResponse.setContentType(theResponseEncoding.getResourceContentType());
|
if (theRequestIsBrowser && myUseBrowserFriendlyContentTypes) {
|
||||||
|
theHttpResponse.setContentType(theResponseEncoding.getBrowserFriendlyBundleContentType());
|
||||||
|
} else {
|
||||||
|
theHttpResponse.setContentType(theResponseEncoding.getBundleContentType());
|
||||||
|
}
|
||||||
theHttpResponse.setCharacterEncoding("UTF-8");
|
theHttpResponse.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
PrintWriter writer = theHttpResponse.getWriter();
|
PrintWriter writer = theHttpResponse.getWriter();
|
||||||
getNewParser(theResponseEncoding).encodeResourceToWriter(theResource, writer);
|
getNewParser(theResponseEncoding, thePrettyPrint).encodeResourceToWriter(theResource, writer);
|
||||||
writer.close();
|
writer.close();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import ch.qos.logback.core.joran.action.ParamAction;
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.model.api.PathSpecification;
|
import ca.uhn.fhir.model.api.PathSpecification;
|
||||||
import ca.uhn.fhir.rest.annotation.Include;
|
import ca.uhn.fhir.rest.annotation.Include;
|
||||||
|
@ -89,7 +88,7 @@ public class Util {
|
||||||
}
|
}
|
||||||
Class<? extends Collection<PathSpecification>> instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + method.getName() + "'");
|
Class<? extends Collection<PathSpecification>> instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + method.getName() + "'");
|
||||||
|
|
||||||
param = new IncludeParameter(instantiableCollectionType);
|
param = new IncludeParameter((Include) nextAnnotation, instantiableCollectionType);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package ca.uhn.fhir.rest.server.provider;
|
package ca.uhn.fhir.rest.server.provider;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -22,13 +21,11 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.rest.annotation.Metadata;
|
import ca.uhn.fhir.rest.annotation.Metadata;
|
||||||
import ca.uhn.fhir.rest.method.BaseMethodBinding;
|
import ca.uhn.fhir.rest.method.BaseMethodBinding;
|
||||||
import ca.uhn.fhir.rest.method.ReadMethodBinding;
|
|
||||||
import ca.uhn.fhir.rest.method.SearchMethodBinding;
|
import ca.uhn.fhir.rest.method.SearchMethodBinding;
|
||||||
import ca.uhn.fhir.rest.param.IParameter;
|
import ca.uhn.fhir.rest.param.IParameter;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
import ca.uhn.fhir.rest.server.ResourceBinding;
|
import ca.uhn.fhir.rest.server.ResourceBinding;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.util.DatatypeUtil;
|
|
||||||
import ca.uhn.fhir.util.ExtensionConstants;
|
import ca.uhn.fhir.util.ExtensionConstants;
|
||||||
import ca.uhn.fhir.util.VersionUtil;
|
import ca.uhn.fhir.util.VersionUtil;
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ public class ElementUtil {
|
||||||
if (!isEmpty((List<? extends IElement>) next)) {
|
if (!isEmpty((List<? extends IElement>) next)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (next instanceof String && (!((String)next).isEmpty())) {
|
||||||
|
return false;
|
||||||
} else if (next != null && !((IElement) next).isEmpty()) {
|
} else if (next != null && !((IElement) next).isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ public class PrettyPrintWriterWrapper implements XMLStreamWriter {
|
||||||
@Override
|
@Override
|
||||||
public void writeStartElement(String thePrefix, String theLocalName, String theNamespaceURI) throws XMLStreamException {
|
public void writeStartElement(String thePrefix, String theLocalName, String theNamespaceURI) throws XMLStreamException {
|
||||||
indentAndAdd();
|
indentAndAdd();
|
||||||
myTarget.writeStartElement(thePrefix, theNamespaceURI, theLocalName);
|
myTarget.writeStartElement(thePrefix, theLocalName, theNamespaceURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -138,8 +138,11 @@ public Class<? extends IResource> getResourceType() {
|
||||||
|
|
||||||
//START SNIPPET: pathSpec
|
//START SNIPPET: pathSpec
|
||||||
@Search()
|
@Search()
|
||||||
public List<DiagnosticReport> getDiagnosticReport( @Required(name=DiagnosticReport.SP_IDENTIFIER) IdentifierDt theIdentifier,
|
public List<DiagnosticReport> getDiagnosticReport(
|
||||||
@Include Set<PathSpecification> theIncludes ) {
|
@Required(name=DiagnosticReport.SP_IDENTIFIER)
|
||||||
|
IdentifierDt theIdentifier,
|
||||||
|
@Include(allow= {"DiagnosticReport.subject"})
|
||||||
|
Set<PathSpecification> theIncludes ) {
|
||||||
List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>();
|
List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>();
|
||||||
|
|
||||||
// Assume this method exists and loads the report from the DB
|
// Assume this method exists and loads the report from the DB
|
||||||
|
|
|
@ -300,7 +300,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Invoking a client of thie type involves the following syntax:
|
Invoking a client of this type involves the following syntax:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<macro name="snippet">
|
<macro name="snippet">
|
||||||
|
@ -328,6 +328,11 @@
|
||||||
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
|
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
|
||||||
</macro>
|
</macro>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Example URL to invoke this method:<br/>
|
||||||
|
<code>http://fhir.example.com/DiagnosticReport?subject.identifier=7000135&_include=DiagnosticReport.subject</code>
|
||||||
|
</p>
|
||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -27,6 +27,8 @@ import ca.uhn.fhir.model.dstu.resource.Observation;
|
||||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||||
import ca.uhn.fhir.model.dstu.resource.Specimen;
|
import ca.uhn.fhir.model.dstu.resource.Specimen;
|
||||||
import ca.uhn.fhir.model.dstu.resource.ValueSet;
|
import ca.uhn.fhir.model.dstu.resource.ValueSet;
|
||||||
|
import ca.uhn.fhir.model.dstu.valueset.AddressUseEnum;
|
||||||
|
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
|
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
|
||||||
import ca.uhn.fhir.model.primitive.DecimalDt;
|
import ca.uhn.fhir.model.primitive.DecimalDt;
|
||||||
|
|
||||||
|
@ -44,6 +46,33 @@ public class XmlParserTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeBoundCode() throws IOException {
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addAddress().setUse(AddressUseEnum.HOME);
|
||||||
|
|
||||||
|
patient.getGender().setValueAsEnum(AdministrativeGenderCodesEnum.M);
|
||||||
|
|
||||||
|
String val = new FhirContext().newXmlParser().encodeResourceToString(patient);
|
||||||
|
ourLog.info(val);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeBundleResultCount() throws IOException {
|
||||||
|
|
||||||
|
Bundle b = new Bundle();
|
||||||
|
b.getTotalResults().setValue(123);
|
||||||
|
|
||||||
|
String val = new FhirContext().newXmlParser().setPrettyPrint(true).encodeBundleToString(b);
|
||||||
|
ourLog.info(val);
|
||||||
|
|
||||||
|
assertThat(val, StringContains.containsString("<os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">123</os:totalResults>"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseContainedResources() throws IOException {
|
public void testParseContainedResources() throws IOException {
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package ca.uhn.fhir.rest.server;
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -23,13 +23,15 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.servlet.ServletHandler;
|
import org.eclipse.jetty.servlet.ServletHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.hamcrest.core.IsNot;
|
||||||
|
import org.hamcrest.core.StringContains;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.experimental.theories.Theories;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
import ca.uhn.fhir.model.api.Bundle;
|
||||||
|
import ca.uhn.fhir.model.api.BundleEntry;
|
||||||
import ca.uhn.fhir.model.api.PathSpecification;
|
import ca.uhn.fhir.model.api.PathSpecification;
|
||||||
import ca.uhn.fhir.model.dstu.composite.CodingDt;
|
import ca.uhn.fhir.model.dstu.composite.CodingDt;
|
||||||
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
||||||
|
@ -104,12 +106,60 @@ public class ResfulServerMethodTest {
|
||||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
||||||
|
|
||||||
Patient patient = (Patient) bundle.getEntries().get(0).getResource();
|
BundleEntry entry0 = bundle.getEntries().get(0);
|
||||||
|
Patient patient = (Patient) entry0.getResource();
|
||||||
assertEquals("include1", patient.getCommunication().get(0).getText().getValue());
|
assertEquals("include1", patient.getCommunication().get(0).getText().getValue());
|
||||||
assertEquals("include2", patient.getAddress().get(0).getLine().get(0).getValue());
|
assertEquals("include2", patient.getAddress().get(0).getLine().get(0).getValue());
|
||||||
assertEquals("include3", patient.getAddress().get(1).getLine().get(0).getValue());
|
assertEquals("include3", patient.getAddress().get(1).getLine().get(0).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithIncludesNone() throws Exception {
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?withIncludes=include1");
|
||||||
|
HttpResponse status = ourClient.execute(httpGet);
|
||||||
|
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
ourLog.info("Response was:\n{}", responseContent);
|
||||||
|
|
||||||
|
// Make sure there is no crash
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithIncludesBad() throws Exception {
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?withIncludes=include1&_include=include2&_include=include4");
|
||||||
|
HttpResponse status = ourClient.execute(httpGet);
|
||||||
|
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
ourLog.info("Response was:\n{}", responseContent);
|
||||||
|
|
||||||
|
assertEquals(400, status.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEntryLinkSelf() throws Exception {
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?withIncludes=include1&_include=include2&_include=include3");
|
||||||
|
HttpResponse status = ourClient.execute(httpGet);
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
||||||
|
BundleEntry entry0 = bundle.getEntries().get(0);
|
||||||
|
assertEquals("http://localhost:" + ourPort + "/Patient/1", entry0.getLinkSelf().getValue());
|
||||||
|
assertEquals("1", entry0.getEntryId().getValue());
|
||||||
|
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?withIncludes=include1&_include=include2&_include=include3&_format=json");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
bundle = ourCtx.newJsonParser().parseBundle(responseContent);
|
||||||
|
entry0 = bundle.getEntries().get(0);
|
||||||
|
assertEquals("http://localhost:" + ourPort + "/Patient/1?_format=json", entry0.getLinkSelf().getValue());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchAllProfiles() throws Exception {
|
public void testSearchAllProfiles() throws Exception {
|
||||||
|
|
||||||
|
@ -216,17 +266,17 @@ public class ResfulServerMethodTest {
|
||||||
assertEquals("urn:bbb|bbb", patient.getIdentifier().get(2).getValueAsQueryToken());
|
assertEquals("urn:bbb|bbb", patient.getIdentifier().get(2).getValueAsQueryToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
// @Test
|
||||||
// public void testSearchByComplex() throws Exception {
|
// public void testSearchByComplex() throws Exception {
|
||||||
//
|
//
|
||||||
// HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?Patient.identifier=urn:oid:2.16.840.1.113883.3.239.18.148%7C7000135&name=urn:oid:1.3.6.1.4.1.12201.102.5%7C522&date=");
|
// HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?Patient.identifier=urn:oid:2.16.840.1.113883.3.239.18.148%7C7000135&name=urn:oid:1.3.6.1.4.1.12201.102.5%7C522&date=");
|
||||||
// HttpResponse status = ourClient.execute(httpGet);
|
// HttpResponse status = ourClient.execute(httpGet);
|
||||||
//
|
//
|
||||||
// String responseContent = IOUtils.toString(status.getEntity().getContent());
|
// String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
// ourLog.info("Response was:\n{}", responseContent);
|
// ourLog.info("Response was:\n{}", responseContent);
|
||||||
//
|
//
|
||||||
// assertEquals(200, status.getStatusLine().getStatusCode());
|
// assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchByDob() throws Exception {
|
public void testSearchByDob() throws Exception {
|
||||||
|
@ -404,6 +454,31 @@ public class ResfulServerMethodTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPrettyPrint() throws Exception {
|
||||||
|
|
||||||
|
// HttpPost httpPost = new HttpPost("http://localhost:" + ourPort +
|
||||||
|
// "/Patient/1");
|
||||||
|
// httpPost.setEntity(new StringEntity("test",
|
||||||
|
// ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
|
||||||
|
HttpResponse status = ourClient.execute(httpGet);
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
assertThat(responseContent, StringContains.containsString("<identifier><use"));
|
||||||
|
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_pretty=false");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
assertThat(responseContent, StringContains.containsString("<identifier><use"));
|
||||||
|
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_pretty=true");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
assertThat(responseContent, IsNot.not(StringContains.containsString("<identifier><use")));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetById() throws Exception {
|
public void testGetById() throws Exception {
|
||||||
|
|
||||||
|
@ -510,6 +585,7 @@ public class ResfulServerMethodTest {
|
||||||
patient.getName().get(0).addFamily("Test");
|
patient.getName().get(0).addFamily("Test");
|
||||||
patient.getName().get(0).addGiven("PatientOne");
|
patient.getName().get(0).addGiven("PatientOne");
|
||||||
patient.getGender().setText("M");
|
patient.getGender().setText("M");
|
||||||
|
patient.getId().setValue("1");
|
||||||
idToPatient.put("1", patient);
|
idToPatient.put("1", patient);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -522,13 +598,15 @@ public class ResfulServerMethodTest {
|
||||||
patient.getName().get(0).addFamily("Test");
|
patient.getName().get(0).addFamily("Test");
|
||||||
patient.getName().get(0).addGiven("PatientTwo");
|
patient.getName().get(0).addGiven("PatientTwo");
|
||||||
patient.getGender().setText("F");
|
patient.getGender().setText("F");
|
||||||
|
patient.getId().setValue("2");
|
||||||
idToPatient.put("2", patient);
|
idToPatient.put("2", patient);
|
||||||
}
|
}
|
||||||
return idToPatient;
|
return idToPatient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Search()
|
@Search()
|
||||||
public Patient getPatientWithIncludes(@Required(name = "withIncludes") StringDt theString, @Include List<PathSpecification> theIncludes) {
|
public Patient getPatientWithIncludes(@Required(name = "withIncludes") StringDt theString,
|
||||||
|
@Include(allow= {"include1","include2", "include3"}) List<PathSpecification> theIncludes) {
|
||||||
Patient next = getIdToPatient().get("1");
|
Patient next = getIdToPatient().get("1");
|
||||||
|
|
||||||
next.addCommunication().setText(theString.getValue());
|
next.addCommunication().setText(theString.getValue());
|
||||||
|
@ -553,11 +631,8 @@ public class ResfulServerMethodTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public List<Patient> findDiagnosticReportsByPatient(
|
public List<Patient> findDiagnosticReportsByPatient(@Required(name = "Patient.identifier") IdentifierDt thePatientId, @Required(name = DiagnosticReport.SP_NAME) CodingListParam theNames,
|
||||||
@Required(name="Patient.identifier") IdentifierDt thePatientId,
|
@Optional(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange) throws Exception {
|
||||||
@Required(name=DiagnosticReport.SP_NAME) CodingListParam theNames,
|
|
||||||
@Optional(name=DiagnosticReport.SP_DATE) DateRangeParam theDateRange
|
|
||||||
) throws Exception {
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue