Many bugfixes

This commit is contained in:
jamesagnew 2014-03-21 15:32:37 -04:00
parent 79bdd94f72
commit cb19b17e59
26 changed files with 398 additions and 116 deletions

View File

@ -33,6 +33,8 @@
* Support "Binary" resource, which is a special resource type
* Submit "_pretty" as possible parameter to HL7
---------
Issues:
* Need to be able to bind Identifier.system to a set of allowable values in a profile. Graeme

View File

@ -8,7 +8,6 @@ import java.util.Set;
import ca.uhn.fhir.model.api.ICodeEnum;
import ca.uhn.fhir.model.api.IDatatype;
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.Description;
@ -35,7 +34,8 @@ public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeDecl
@Override
public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IElement> theDatatype) {
if (myDatatype.equals(theDatatype)) {
Class<? extends IElement> datatype = theDatatype;
if (myDatatype.equals(datatype)) {
return myElementDefinition;
}
return null;
@ -74,4 +74,11 @@ public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeDecl
}
myCodeType = theType;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + getElementName() + "]";
}
}

View File

@ -12,6 +12,5 @@ public class RuntimeChildCompositeDatatypeDefinition extends BaseRuntimeChildDat
super(theField, theElementName, theChildAnnotation,theDescriptionAnnotation, theDatatype);
}
}

View File

@ -83,4 +83,9 @@ public class RuntimeChildResourceDefinition extends BaseRuntimeDeclaredChildDefi
myValidChildNames = Collections.unmodifiableSet(myValidChildNames);
}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + getElementName() + "]";
}
}

View File

@ -30,7 +30,7 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
return;
}
for (T next : theValues) {
getCoding().add(new BoundCodingDt<T>(myBinder, next));
getCoding().add(new CodingDt(myBinder.toSystemString(next), myBinder.toCodeString(next)));
}
}

View File

@ -5,16 +5,16 @@ import ca.uhn.fhir.model.api.IValueSetEnumBinder;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.dstu.composite.CodingDt;
@DatatypeDef(name = "Coding")
public class BoundCodingDt<T extends Enum<?>> extends CodingDt {
@DatatypeDef(name = "Coding", isSpecialization=true)
public class BoundCodingDt__<T extends Enum<?>> extends CodingDt {
private IValueSetEnumBinder<T> myBinder;
public BoundCodingDt(IValueSetEnumBinder<T> theBinder) {
public BoundCodingDt__(IValueSetEnumBinder<T> theBinder) {
myBinder = theBinder;
}
public BoundCodingDt(IValueSetEnumBinder<T> theBinder, T theValue) {
public BoundCodingDt__(IValueSetEnumBinder<T> theBinder, T theValue) {
myBinder = theBinder;
setValueAsEnum(theValue);
}

View File

@ -8,10 +8,8 @@ import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
/**
* 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.
*
* 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.
*
* <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
* limit of 36 characters.
@ -32,6 +30,17 @@ public class IdDt extends BasePrimitive<String> {
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
*/
@ -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) {
if (thePid != null) {
setValue(thePid.toPlainString());
} else {
setValue(null);
public BigDecimal asBigDecimal() {
if (getValue() == null) {
return 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
@ -76,6 +95,14 @@ public class IdDt extends BasePrimitive<String> {
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
*
@ -114,16 +141,4 @@ public class IdDt extends BasePrimitive<String> {
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());
}
}

View File

@ -101,5 +101,13 @@ public class StringDt extends BasePrimitive<String> implements IQueryParameterTy
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());
}
}

View File

@ -99,7 +99,7 @@ public class JsonParser extends BaseParser implements IParser {
writeTagWithTextNode(eventWriter, "id", nextEntry.getEntryId());
eventWriter.writeStartArray("link");
writeAtomLink(eventWriter, "self", theBundle.getLinkSelf());
writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf());
eventWriter.writeEnd();
writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());

View File

@ -100,7 +100,7 @@ public class XmlParser extends BaseParser implements IParser {
writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase());
if (theBundle.getTotalResults().getValue() != null) {
eventWriter.writeStartElement("os", "totalResults",OPENSEARCH_NS);
eventWriter.writeStartElement("os", "totalResults", OPENSEARCH_NS);
eventWriter.writeNamespace("os", OPENSEARCH_NS);
eventWriter.writeCharacters(theBundle.getTotalResults().getValue().toString());
eventWriter.writeEndElement();
@ -119,6 +119,13 @@ public class XmlParser extends BaseParser implements IParser {
for (BundleEntry nextEntry : theBundle.getEntries()) {
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.writeAttribute("type", "text/xml");
@ -138,10 +145,10 @@ public class XmlParser extends BaseParser implements IParser {
@Override
public String encodeResourceToString(IResource theResource) throws DataFormatException {
if (theResource==null) {
if (theResource == null) {
throw new NullPointerException("Resource can not be null");
}
Writer stringWriter = new StringWriter();
encodeResourceToWriter(theResource, stringWriter);
return stringWriter.toString();
@ -200,8 +207,6 @@ public class XmlParser extends BaseParser implements IParser {
return parseResource(theResourceType, streamReader);
}
@Override
public IParser setPrettyPrint(boolean 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()) {
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) {
List<? extends IElement> values = nextChild.getAccessor().getValues(theElement);
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);
encodeCompositeElementChildrenToStreamWriter(theElement, theEventWriter, resDef.getExtensions());
encodeCompositeElementChildrenToStreamWriter(theElement, theEventWriter, resDef.getChildren());
@ -419,22 +427,21 @@ public class XmlParser extends BaseParser implements IParser {
}
}
private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludeResourceId) throws XMLStreamException, DataFormatException {
super.containResourcesForEncoding(theResource);
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
if (resDef == null) {
throw new ConfigurationException("Unknown resource type: " + theResource.getClass());
}
theEventWriter.writeStartElement(resDef.getName());
theEventWriter.writeDefaultNamespace(FHIR_NS);
if (theIncludeResourceId && StringUtils.isNotBlank(theResource.getId().getValue())) {
theEventWriter.writeAttribute("id", theResource.getId().getValue());
}
encodeCompositeElementToStreamWriter(theResource, theEventWriter, resDef);
theEventWriter.writeEndElement();
@ -449,7 +456,7 @@ public class XmlParser extends BaseParser implements IParser {
IElement nextValue = next.getValue();
RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
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());
}
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 {
theEventWriter.writeStartElement(theElementName);
if (StringUtils.isNotBlank(theStringDt.getValue())) {

View File

@ -10,11 +10,23 @@ import java.lang.annotation.Target;
* be populated with the "_include" values for a search param.
* <p>
* 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>
* <ul>
* <li><code>Collection&lt;PathSpecification&gt;</code></li>
* <li><code>List&lt;PathSpecification&gt;</code></li>
* <li><code>Set&lt;PathSpecification&gt;</code></li>
* </ul>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value= {ElementType.PARAMETER})
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 {};
}

View File

@ -106,6 +106,9 @@ public class SearchMethodBinding extends BaseMethodBinding {
IParameter param = myParameters.get(i);
String[] value = parameterValues.get(param.getName());
if (value == null || value.length == 0) {
if (param.handlesMissing()) {
params[i] = param.parse(new ArrayList<List<String>>(0));
}
continue;
}

View File

@ -8,14 +8,20 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
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();
/**
* 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();
public abstract SearchParamTypeEnum getParamType();
SearchParamTypeEnum getParamType();
}

View File

@ -3,19 +3,29 @@ package ca.uhn.fhir.rest.param;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
import ca.uhn.fhir.model.api.PathSpecification;
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.InvalidRequestException;
public class IncludeParameter implements IParameter {
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;
if (theAnnotation.allow().length > 0) {
myAllow = new HashSet<String>();
for (String next : theAnnotation.allow()) {
myAllow.add(next);
}
}
}
@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");
}
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;
@ -69,4 +85,9 @@ public class IncludeParameter implements IParameter {
return null;
}
@Override
public boolean handlesMissing() {
return true;
}
}

View File

@ -100,6 +100,8 @@ public class SearchParameter implements IParameter {
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) {
// this.parser = new CollectionBinder(this.parser, theInnerCollectionType);
// }
@ -115,4 +117,9 @@ public class SearchParameter implements IParameter {
return myParamType;
}
@Override
public boolean handlesMissing() {
return false;
}
}

View File

@ -16,6 +16,8 @@ public class Constants {
public static final Set<String> FORMAT_VAL_XML;
public static final Set<String> FORMAT_VAL_JSON;
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 {
Map<String, EncodingUtil> valToEncoding = new HashMap<String, EncodingUtil>();

View File

@ -2,16 +2,20 @@ package ca.uhn.fhir.rest.server;
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 myBundleContentType;
EncodingUtil(String theResourceContentType, String theBundleContentType) {
private String myBrowserFriendlyContentType;
EncodingUtil(String theResourceContentType, String theBundleContentType, String theBrowserFriendlyContentType) {
myResourceContentType = theResourceContentType;
myBundleContentType = theBundleContentType;
myBrowserFriendlyContentType = theBrowserFriendlyContentType;
}
public String getBundleContentType() {
@ -22,4 +26,8 @@ public enum EncodingUtil {
return myResourceContentType;
}
public String getBrowserFriendlyBundleContentType() {
return myBrowserFriendlyContentType;
}
}

View File

@ -40,11 +40,24 @@ import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
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 long serialVersionUID = 1L;
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>();
@ -75,18 +88,17 @@ public abstract class RestfulServer extends HttpServlet {
}
return EncodingUtil.XML;
}
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.GET, request, response);
}
@Override
protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.OPTIONS, theReq, theResp);
@ -102,7 +114,6 @@ public abstract class RestfulServer extends HttpServlet {
handleRequest(SearchMethodBinding.RequestType.PUT, request, response);
}
private void findResourceMethods(IResourceProvider theProvider) throws Exception {
Class<? extends IResource> resourceType = theProvider.getResourceType();
@ -131,12 +142,11 @@ public abstract class RestfulServer extends HttpServlet {
}
}
public FhirContext getFhirContext() {
return myFhirContext;
}
private IParser getNewParser(EncodingUtil theResponseEncoding) {
private IParser getNewParser(EncodingUtil theResponseEncoding, boolean thePrettyPrint) {
IParser parser;
switch (theResponseEncoding) {
case JSON:
@ -147,7 +157,7 @@ public abstract class RestfulServer extends HttpServlet {
parser = myFhirContext.newXmlParser();
break;
}
return parser;
return parser.setPrettyPrint(thePrettyPrint);
}
public Collection<ResourceBinding> getResourceBindings() {
@ -160,8 +170,7 @@ public abstract class RestfulServer extends HttpServlet {
public abstract Collection<IResourceProvider> getResourceProviders();
/**
* This method should be overridden to provide a security manager instance.
* By default, returns null.
* This method should be overridden to provide a security manager instance. By default, returns null.
*/
public ISecurityManager getSecurityManager() {
return null;
@ -181,40 +190,61 @@ public abstract class RestfulServer extends HttpServlet {
if (null != securityManager) {
securityManager.authenticate(request);
}
String uaHeader = request.getHeader("user-agent");
boolean requestIsBrowser = false;
if (uaHeader != null && uaHeader.contains("Mozilla")) {
requestIsBrowser = true;
}
String resourceName = null;
String requestFullPath = StringUtils.defaultString(request.getRequestURI());
// String contextPath = StringUtils.defaultString(request.getContextPath());
// String contextPath = StringUtils.defaultString(request.getContextPath());
String servletPath = StringUtils.defaultString(request.getServletPath());
StringBuffer requestUrl = request.getRequestURL();
String servletContextPath = "";
if (request.getServletContext() != null) {
servletContextPath = StringUtils.defaultString(request.getServletContext().getContextPath());
}
ourLog.info("Request FullPath: {}", requestFullPath);
ourLog.info("Servlet Path: {}", servletPath);
ourLog.info("Request Url: {}", requestUrl);
ourLog.info("Context Path: {}", servletContextPath);
servletPath = servletContextPath;
IdDt id = null;
IdDt versionId = null;
String operation = null;
String operation = null;
String requestPath = requestFullPath.substring(servletPath.length());
if (requestPath.length() > 0 && requestPath.charAt(0) == '/') {
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 completeUrl = StringUtils.isNotBlank(request.getQueryString()) ? requestUrl + "?" + request.getQueryString() : requestUrl.toString();
Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());
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, "/");
if (!tok.hasMoreTokens()) {
throw new MethodNotFoundException("No resource name specified");
@ -228,7 +258,7 @@ public abstract class RestfulServer extends HttpServlet {
} else {
resourceBinding = resources.get(resourceName);
}
if (resourceBinding == null) {
throw new MethodNotFoundException("Unknown resource type: " + resourceName);
}
@ -244,7 +274,7 @@ public abstract class RestfulServer extends HttpServlet {
if (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
if (nextString.startsWith("_history")) {
if (nextString.startsWith(PARAM_HISTORY)) {
if (tok.hasMoreTokens()) {
versionId = new IdDt(tok.nextToken());
} else {
@ -271,7 +301,7 @@ public abstract class RestfulServer extends HttpServlet {
List<IResource> result = resourceMethod.invokeServer(resourceBinding.getResourceProvider(), id, versionId, params);
switch (resourceMethod.getReturnType()) {
case BUNDLE:
streamResponseAsBundle(response, result, responseEncoding, fhirServerBase, completeUrl);
streamResponseAsBundle(response, result, responseEncoding, fhirServerBase, completeUrl, prettyPrint, requestIsBrowser);
break;
case RESOURCE:
if (result.size() == 0) {
@ -279,7 +309,7 @@ public abstract class RestfulServer extends HttpServlet {
} else if (result.size() > 1) {
throw new InternalErrorException("Method returned multiple resources");
}
streamResponseAsResource(response, result.get(0), resourceBinding, responseEncoding);
streamResponseAsResource(response, result.get(0), responseEncoding, prettyPrint, requestIsBrowser);
break;
}
// resourceMethod.get
@ -334,7 +364,7 @@ public abstract class RestfulServer extends HttpServlet {
for (IResourceProvider provider : myTypeToProvider.values()) {
findResourceMethods(provider);
}
findResourceMethods(getServerProfilesProvider());
findResourceMethods(getServerConformanceProvider());
@ -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.setContentType(theResponseEncoding.getBundleContentType());
if (theRequestIsBrowser && myUseBrowserFriendlyContentTypes) {
theHttpResponse.setContentType(theResponseEncoding.getBrowserFriendlyBundleContentType());
} else {
theHttpResponse.setContentType(theResponseEncoding.getBundleContentType());
}
theHttpResponse.setCharacterEncoding("UTF-8");
Bundle bundle = new Bundle();
bundle.getAuthorName().setValue(getClass().getCanonicalName());
bundle.getBundleId().setValue(UUID.randomUUID().toString());
bundle.getPublished().setToCurrentTimeInLocalTimeZone();
bundle.getLinkBase().setValue(theFhirServerBase);
bundle.getLinkBase().setValue(theServerBase);
bundle.getLinkSelf().setValue(theCompleteUrl);
for (IResource next : theResult) {
@ -361,23 +400,55 @@ public abstract class RestfulServer extends HttpServlet {
bundle.getEntries().add(entry);
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());
PrintWriter writer = theHttpResponse.getWriter();
getNewParser(theResponseEncoding).encodeBundleToWriter(bundle, writer);
getNewParser(theResponseEncoding, thePrettyPrint).encodeBundleToWriter(bundle, writer);
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.setContentType(theResponseEncoding.getResourceContentType());
if (theRequestIsBrowser && myUseBrowserFriendlyContentTypes) {
theHttpResponse.setContentType(theResponseEncoding.getBrowserFriendlyBundleContentType());
} else {
theHttpResponse.setContentType(theResponseEncoding.getBundleContentType());
}
theHttpResponse.setCharacterEncoding("UTF-8");
PrintWriter writer = theHttpResponse.getWriter();
getNewParser(theResponseEncoding).encodeResourceToWriter(theResource, writer);
getNewParser(theResponseEncoding, thePrettyPrint).encodeResourceToWriter(theResource, writer);
writer.close();
}

View File

@ -10,7 +10,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ch.qos.logback.core.joran.action.ParamAction;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.PathSpecification;
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() + "'");
param = new IncludeParameter(instantiableCollectionType);
param = new IncludeParameter((Include) nextAnnotation, instantiableCollectionType);
} else {
continue;
}

View File

@ -1,6 +1,5 @@
package ca.uhn.fhir.rest.server.provider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
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.rest.annotation.Metadata;
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.param.IParameter;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.ResourceBinding;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.DatatypeUtil;
import ca.uhn.fhir.util.ExtensionConstants;
import ca.uhn.fhir.util.VersionUtil;

View File

@ -19,6 +19,8 @@ public class ElementUtil {
if (!isEmpty((List<? extends IElement>) next)) {
return false;
}
} else if (next instanceof String && (!((String)next).isEmpty())) {
return false;
} else if (next != null && !((IElement) next).isEmpty()) {
return false;
}

View File

@ -94,7 +94,7 @@ public class PrettyPrintWriterWrapper implements XMLStreamWriter {
@Override
public void writeStartElement(String thePrefix, String theLocalName, String theNamespaceURI) throws XMLStreamException {
indentAndAdd();
myTarget.writeStartElement(thePrefix, theNamespaceURI, theLocalName);
myTarget.writeStartElement(thePrefix, theLocalName, theNamespaceURI);
}
@Override

View File

@ -138,8 +138,11 @@ public Class<? extends IResource> getResourceType() {
//START SNIPPET: pathSpec
@Search()
public List<DiagnosticReport> getDiagnosticReport( @Required(name=DiagnosticReport.SP_IDENTIFIER) IdentifierDt theIdentifier,
@Include Set<PathSpecification> theIncludes ) {
public List<DiagnosticReport> getDiagnosticReport(
@Required(name=DiagnosticReport.SP_IDENTIFIER)
IdentifierDt theIdentifier,
@Include(allow= {"DiagnosticReport.subject"})
Set<PathSpecification> theIncludes ) {
List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>();
// Assume this method exists and loads the report from the DB

View File

@ -300,7 +300,7 @@
</p>
<p>
Invoking a client of thie type involves the following syntax:
Invoking a client of this type involves the following syntax:
</p>
<macro name="snippet">
@ -328,6 +328,11 @@
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Example URL to invoke this method:<br/>
<code>http://fhir.example.com/DiagnosticReport?subject.identifier=7000135&amp;_include=DiagnosticReport.subject</code>
</p>
</subsection>
</section>

View File

@ -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.Specimen;
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.primitive.DecimalDt;
@ -43,6 +45,33 @@ public class XmlParserTest {
assertEquals("Patient resource with id 3216379", bundle.getEntries().get(0).getTitle().getValue());
}
@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
public void testParseContainedResources() throws IOException {

View File

@ -1,9 +1,9 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
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.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hamcrest.core.IsNot;
import org.hamcrest.core.StringContains;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.theories.Theories;
import ca.uhn.fhir.context.FhirContext;
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.dstu.composite.CodingDt;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
@ -104,12 +106,60 @@ public class ResfulServerMethodTest {
assertEquals(200, status.getStatusLine().getStatusCode());
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("include2", patient.getAddress().get(0).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
public void testSearchAllProfiles() throws Exception {
@ -216,18 +266,18 @@ public class ResfulServerMethodTest {
assertEquals("urn:bbb|bbb", patient.getIdentifier().get(2).getValueAsQueryToken());
}
// @Test
// 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=");
// HttpResponse status = ourClient.execute(httpGet);
//
// String responseContent = IOUtils.toString(status.getEntity().getContent());
// ourLog.info("Response was:\n{}", responseContent);
//
// assertEquals(200, status.getStatusLine().getStatusCode());
// }
// @Test
// 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=");
// HttpResponse status = ourClient.execute(httpGet);
//
// String responseContent = IOUtils.toString(status.getEntity().getContent());
// ourLog.info("Response was:\n{}", responseContent);
//
// assertEquals(200, status.getStatusLine().getStatusCode());
// }
@Test
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
public void testGetById() throws Exception {
@ -510,6 +585,7 @@ public class ResfulServerMethodTest {
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
patient.getId().setValue("1");
idToPatient.put("1", patient);
}
{
@ -522,13 +598,15 @@ public class ResfulServerMethodTest {
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientTwo");
patient.getGender().setText("F");
patient.getId().setValue("2");
idToPatient.put("2", patient);
}
return idToPatient;
}
@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");
next.addCommunication().setText(theString.getValue());
@ -553,14 +631,11 @@ public class ResfulServerMethodTest {
}
@SuppressWarnings("unused")
public List<Patient> findDiagnosticReportsByPatient(
@Required(name="Patient.identifier") IdentifierDt thePatientId,
@Required(name=DiagnosticReport.SP_NAME) CodingListParam theNames,
@Optional(name=DiagnosticReport.SP_DATE) DateRangeParam theDateRange
) throws Exception {
public List<Patient> findDiagnosticReportsByPatient(@Required(name = "Patient.identifier") IdentifierDt thePatientId, @Required(name = DiagnosticReport.SP_NAME) CodingListParam theNames,
@Optional(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange) throws Exception {
return Collections.emptyList();
}
@Search()
public Patient getPatientWithDOB(@Required(name = "dob") QualifiedDateParam theDob) {
Patient next = getIdToPatient().get("1");