More work on the tester, and a number of bug fixes

This commit is contained in:
jamesagnew 2014-05-02 19:56:26 -07:00
parent a1aedf2f31
commit 25a40df96e
43 changed files with 2130 additions and 874 deletions

View File

@ -20,8 +20,11 @@ package ca.uhn.fhir.parser;
* #L%
*/
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -54,6 +57,32 @@ public abstract class BaseParser implements IParser {
return parseBundle(reader);
}
@Override
public String encodeResourceToString(IResource theResource) throws DataFormatException {
Writer stringWriter = new StringWriter();
try {
encodeResourceToWriter(theResource, stringWriter);
} catch (IOException e) {
throw new Error("Encountered IOException during write to string - This should not happen!");
}
return stringWriter.toString();
}
@Override
public String encodeBundleToString(Bundle theBundle) throws DataFormatException {
if (theBundle == null) {
throw new NullPointerException("Bundle can not be null");
}
StringWriter stringWriter = new StringWriter();
try {
encodeBundleToWriter(theBundle, stringWriter);
} catch (IOException e) {
throw new Error("Encountered IOException during write to string - This should not happen!");
}
return stringWriter.toString();
}
@Override
public IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException {
return parseResource(null, theMessageString);

View File

@ -30,11 +30,11 @@ import ca.uhn.fhir.model.api.IResource;
public interface IParser {
String encodeBundleToString(Bundle theBundle) throws DataFormatException, IOException;
String encodeBundleToString(Bundle theBundle) throws DataFormatException;
void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException, DataFormatException;
String encodeResourceToString(IResource theResource) throws DataFormatException, IOException;
String encodeResourceToString(IResource theResource) throws DataFormatException;
void encodeResourceToWriter(IResource theResource, Writer stringWriter) throws IOException, DataFormatException;
@ -44,19 +44,57 @@ public interface IParser {
Bundle parseBundle(String theMessageString) throws ConfigurationException, DataFormatException;
<T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader);
/**
* Parses a resource
*
* @param theResourceType
* The resource type to use. This can be used to explicitly
* specify a class which extends a built-in type (e.g. a custom
* type extending the default Patient class)
* @param theReader
* The reader to parse inpou from
* @return A parsed resource
* @throws DataFormatException
* If the resource can not be parsed because the data is not
* recognized or invalid for any reason
*/
<T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
<T extends IResource> T parseResource(Class<T> theResourceType, String theMessageString);
/**
* Parses a resource
*
* @param theResourceType
* The resource type to use. This can be used to explicitly
* specify a class which extends a built-in type (e.g. a custom
* type extending the default Patient class)
* @param theString
* The string to parse
* @return A parsed resource
* @throws DataFormatException
* If the resource can not be parsed because the data is not
* recognized or invalid for any reason
*/
<T extends IResource> T parseResource(Class<T> theResourceType, String theString) throws DataFormatException;
IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException;
IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException;
/**
* Sets the "pretty print" flag, meaning that the parser will encode
* resources with human-readable spacing and newlines between elements
* instead of condensing output as much as possible.
*
* @param thePrettyPrint
* The flag
* @return Returns an instance of <code>this</code> parser so that method
* calls can be conveniently chained
*/
IParser setPrettyPrint(boolean thePrettyPrint);
/**
* If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the
* encoded values.
* If set to <code>true</code> (default is <code>false</code>), narratives
* will not be included in the encoded values.
*/
IParser setSuppressNarratives(boolean theSuppressNarratives);

View File

@ -20,13 +20,11 @@ package ca.uhn.fhir.parser;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
@ -50,7 +48,6 @@ import javax.json.stream.JsonGeneratorFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.text.WordUtils;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
@ -145,17 +142,6 @@ public class JsonParser extends BaseParser implements IParser {
return eventWriter;
}
@Override
public String encodeBundleToString(Bundle theBundle) throws DataFormatException, IOException {
if (theBundle == null) {
throw new NullPointerException("Bundle can not be null");
}
StringWriter stringWriter = new StringWriter();
encodeBundleToWriter(theBundle, stringWriter);
return stringWriter.toString();
}
@Override
public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException {
JsonGenerator eventWriter = createJsonGenerator(theWriter);
@ -473,12 +459,6 @@ public class JsonParser extends BaseParser implements IParser {
theEventWriter.writeEnd();
}
@Override
public String encodeResourceToString(IResource theResource) throws DataFormatException, IOException {
Writer stringWriter = new StringWriter();
encodeResourceToWriter(theResource, stringWriter);
return stringWriter.toString();
}
@Override
public void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException {

View File

@ -0,0 +1,26 @@
package ca.uhn.fhir.rest.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.rest.api.SortSpec;
/**
* For searches, a parameter may be annotated with the {@link Sort} annotation. The
* parameter should be of type {@link SortSpec}.
*
* <p>
* Note that if you wish to chain
* multiple sort parameters (i.e. a sub sort), you should use the {@link SortSpec#setChain(SortSpec)}
* method. Multiple parameters should not be annotated with the Sort annotation.
* </p>
*
* @see Search
*/
@Target(value=ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sort {
// nothing
}

View File

@ -51,6 +51,13 @@ public class MethodOutcome {
return myId;
}
/**
* Returns the {@link OperationOutcome} resource to return to the client or
* <code>null</code> if none.
*
* @return This method <b>will return null</b>, unlike many methods in the
* API.
*/
public OperationOutcome getOperationOutcome() {
return myOperationOutcome;
}
@ -63,6 +70,10 @@ public class MethodOutcome {
myId = theId;
}
/**
* Sets the {@link OperationOutcome} resource to return to the client. Set
* to <code>null</code> (which is the default) if none.
*/
public void setOperationOutcome(OperationOutcome theOperationOutcome) {
myOperationOutcome = theOperationOutcome;
}

View File

@ -0,0 +1,8 @@
package ca.uhn.fhir.rest.api;
public enum SortOrderEnum {
ASC,
DESC
}

View File

@ -0,0 +1,69 @@
package ca.uhn.fhir.rest.api;
/**
* Represents values for <a
* href="http://hl7.org/implement/standards/fhir/search.html#sort">sorting</a>
* resources returned by a server.
*/
public class SortSpec {
private String myFieldName;
private SortSpec myChain;
/**
* Gets the chained sort specification, or <code>null</code> if none. If
* multiple sort parameters are chained (indicating a sub-sort), the second
* level sort is chained via this property.
*/
public SortSpec getChain() {
return myChain;
}
/**
* Sets the chained sort specification, or <code>null</code> if none. If
* multiple sort parameters are chained (indicating a sub-sort), the second
* level sort is chained via this property.
*/
public void setChain(SortSpec theChain) {
myChain = theChain;
}
private SortOrderEnum myOrder;
/**
* Returns the actual name of the field to sort by
*/
public String getFieldName() {
return myFieldName;
}
/**
* Returns the sort order specified by this parameter, or <code>null</code>
* if none is explicitly defined (which means {@link SortOrderEnum#ASC}
* according to the <a
* href="http://hl7.org/implement/standards/fhir/search.html#sort">FHIR
* specification</a>)
*/
public SortOrderEnum getOrder() {
return myOrder;
}
/**
* Sets the actual name of the field to sort by
*/
public void setFieldName(String theFieldName) {
myFieldName = theFieldName;
}
/**
* Sets the sort order specified by this parameter, or <code>null</code> if
* none is explicitly defined (which means {@link SortOrderEnum#ASC}
* according to the <a
* href="http://hl7.org/implement/standards/fhir/search.html#sort">FHIR
* specification</a>)
*/
public void setOrder(SortOrderEnum theOrder) {
myOrder = theOrder;
}
}

View File

@ -26,7 +26,9 @@ import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -42,6 +44,8 @@ import org.apache.http.entity.ContentType;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.method.IClientResponseHandler;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
public abstract class BaseClient {
@ -52,6 +56,29 @@ public abstract class BaseClient {
private HttpResponse myLastResponse;
private String myLastResponseBody;
private final String myUrlBase;
private EncodingEnum myEncoding = null; // default unspecified (will be XML)
private boolean myPrettyPrint = false;
/**
* Returns the encoding that will be used on requests. Default is
* <code>null</code>, which means the client will not explicitly request an
* encoding. (This is standard behaviour according to the FHIR
* specification)
*/
public EncodingEnum getEncoding() {
return myEncoding;
}
/**
* Sets the encoding that will be used on requests. Default is
* <code>null</code>, which means the client will not explicitly request an
* encoding. (This is standard behaviour according to the FHIR
* specification)
*/
public BaseClient setEncoding(EncodingEnum theEncoding) {
myEncoding = theEncoding;
return this;
}
BaseClient(HttpClient theClient, String theUrlBase) {
super();
@ -60,14 +87,16 @@ public abstract class BaseClient {
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
* For now, this is a part of the internal API of HAPI - Use with caution as
* this method may change!
*/
public HttpResponse getLastResponse() {
return myLastResponse;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
* For now, this is a part of the internal API of HAPI - Use with caution as
* this method may change!
*/
public String getLastResponseBody() {
return myLastResponseBody;
@ -83,17 +112,13 @@ public abstract class BaseClient {
HttpRequestBase httpRequest;
HttpResponse response;
try {
httpRequest = clientInvocation.asHttpRequest(myUrlBase);
httpRequest = clientInvocation.asHttpRequest(myUrlBase, createExtraParams());
response = myClient.execute(httpRequest);
} catch (DataFormatException e) {
throw new FhirClientConnectionException(e);
} catch (IOException e) {
throw new FhirClientConnectionException(e);
}
if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() > 299) {
throw BaseServerResponseException.newInstance(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
}
try {
@ -103,14 +128,14 @@ public abstract class BaseClient {
String responseString = IOUtils.toString(reader);
if (myKeepResponses) {
myLastResponse = response;
myLastResponseBody = responseString;
myLastResponseBody = responseString;
}
ourLog.trace("FHIR response:\n{}\n{}", response, responseString);
reader = new StringReader(responseString);
}
ContentType ct = ContentType.get(response.getEntity());
String mimeType = ct.getMimeType();
String mimeType = ct != null ? ct.getMimeType() : null;
Map<String, List<String>> headers = new HashMap<String, List<String>>();
if (response.getAllHeaders() != null) {
@ -125,6 +150,10 @@ public abstract class BaseClient {
}
}
if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() > 299) {
throw BaseServerResponseException.newInstance(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
}
try {
return binding.invokeClient(mimeType, reader, response.getStatusLine().getStatusCode(), headers);
} finally {
@ -145,29 +174,50 @@ public abstract class BaseClient {
}
}
}
protected Map<String, List<String>> createExtraParams() {
HashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>();
if (getEncoding() == EncodingEnum.XML) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList(Constants.CT_XML));
} else if (getEncoding() == EncodingEnum.JSON) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList(Constants.CT_JSON));
}
if (isPrettyPrint()) {
retVal.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
}
return retVal;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
* For now, this is a part of the internal API of HAPI - Use with caution as
* this method may change!
*/
public boolean isKeepResponses() {
return myKeepResponses;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
* For now, this is a part of the internal API of HAPI - Use with caution as
* this method may change!
*/
public void setKeepResponses(boolean theKeepResponses) {
myKeepResponses = theKeepResponses;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
* For now, this is a part of the internal API of HAPI - Use with caution as
* this method may change!
*/
public void setLastResponse(HttpResponse theLastResponse) {
myLastResponse = theLastResponse;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
* For now, this is a part of the internal API of HAPI - Use with caution as
* this method may change!
*/
public void setLastResponseBody(String theLastResponseBody) {
myLastResponseBody = theLastResponseBody;
@ -192,4 +242,25 @@ public abstract class BaseClient {
return reader;
}
/**
* Returns the pretty print flag, which is a request to the server for it to
* return "pretty printed" responses. Note that this is currently a
* non-standard flag (_pretty) which is supported only by HAPI based servers
* (and any other servers which might implement it).
*/
public boolean isPrettyPrint() {
return myPrettyPrint;
}
/**
* Sets the pretty print flag, which is a request to the server for it to
* return "pretty printed" responses. Note that this is currently a
* non-standard flag (_pretty) which is supported only by HAPI based servers
* (and any other servers which might implement it).
*/
public BaseClient setPrettyPrint(boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
return this;
}
}

View File

@ -20,19 +20,48 @@ package ca.uhn.fhir.rest.client;
* #L%
*/
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.http.client.methods.HttpRequestBase;
import ca.uhn.fhir.parser.DataFormatException;
public abstract class BaseClientInvocation {
/**
* Create an HTTP request out of this client request
*
* @param theUrlBase The FHIR server base url (with a trailing "/")
* @param theUrlBase
* The FHIR server base url (with a trailing "/")
* @param theExtraParams
* Any extra request parameters the server wishes to add
*/
public abstract HttpRequestBase asHttpRequest(String theUrlBase) throws DataFormatException, IOException;
public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams);
protected static void appendExtraParamsWithQuestionMark(Map<String, List<String>> theExtraParams, StringBuilder theUrlBuilder, boolean theWithQuestionMark) {
boolean first = theWithQuestionMark;
if (theExtraParams != null && theExtraParams.isEmpty() == false) {
for (Entry<String, List<String>> next : theExtraParams.entrySet()) {
for (String nextValue : next.getValue()) {
if (first) {
theUrlBuilder.append('?');
first = false;
} else {
theUrlBuilder.append('&');
}
try {
theUrlBuilder.append(URLEncoder.encode(next.getKey(), "UTF-8"));
theUrlBuilder.append('=');
theUrlBuilder.append(URLEncoder.encode(nextValue, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported - This should not happen");
}
}
}
}
}
}

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.rest.client;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
@ -68,14 +69,16 @@ public abstract class BaseClientInvocationWithContents extends BaseClientInvocat
}
@Override
public HttpRequestBase asHttpRequest(String theUrlBase) throws DataFormatException, IOException {
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams) throws DataFormatException {
StringBuilder b = new StringBuilder();
b.append(theUrlBase);
if (!theUrlBase.endsWith("/")) {
b.append('/');
}
b.append(StringUtils.defaultString(myUrlExtension));
appendExtraParamsWithQuestionMark(theExtraParams, b, true);
String url = b.toString();
String contents = myContext.newXmlParser().encodeResourceToString(myResource);
StringEntity entity = new StringEntity(contents, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"));

View File

@ -20,14 +20,13 @@ package ca.uhn.fhir.rest.client;
* #L%
*/
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpRequestBase;
import ca.uhn.fhir.parser.DataFormatException;
public class DeleteClientInvocation extends BaseClientInvocation {
private String myUrlPath;
@ -38,7 +37,7 @@ public class DeleteClientInvocation extends BaseClientInvocation {
}
@Override
public HttpRequestBase asHttpRequest(String theUrlBase) throws DataFormatException, IOException {
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams) {
StringBuilder b = new StringBuilder();
b.append(theUrlBase);
if (!theUrlBase.endsWith("/")) {
@ -46,6 +45,8 @@ public class DeleteClientInvocation extends BaseClientInvocation {
}
b.append(myUrlPath);
appendExtraParamsWithQuestionMark(theExtraParams, b,true);
HttpDelete retVal = new HttpDelete(b.toString());
return retVal;
}

View File

@ -32,15 +32,25 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpRequestBase;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.method.BaseOutcomeReturningMethodBinding;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
import ca.uhn.fhir.rest.method.CreateMethodBinding;
import ca.uhn.fhir.rest.method.DeleteMethodBinding;
import ca.uhn.fhir.rest.method.HistoryMethodBinding;
import ca.uhn.fhir.rest.method.IClientResponseHandler;
import ca.uhn.fhir.rest.method.ReadMethodBinding;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.method.UpdateMethodBinding;
import ca.uhn.fhir.rest.method.ValidateMethodBinding;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
public class GenericClient extends BaseClient implements IGenericClient {
@ -49,7 +59,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
private HttpRequestBase myLastRequest;
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
* For now, this is a part of the internal API of HAPI - Use with caution as
* this method may change!
*/
public GenericClient(FhirContext theContext, HttpClient theHttpClient, String theServerBase) {
super(theHttpClient, theServerBase);
@ -64,14 +75,13 @@ public class GenericClient extends BaseClient implements IGenericClient {
public <T extends IResource> T read(final Class<T> theType, IdDt theId) {
GetClientInvocation invocation = ReadMethodBinding.createReadInvocation(theId, toResourceName(theType));
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams());
}
IClientResponseHandler binding = new IClientResponseHandler() {
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
EncodingUtil respType = EncodingUtil.forContentType(theResponseMimeType);
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
IParser parser = respType.newParser(myContext);
return parser.parseResource(theType, theResponseReader);
}
@ -82,8 +92,30 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp;
}
@Override
public MethodOutcome delete(final Class<? extends IResource> theType, IdDt theId) {
DeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(toResourceName(theType), theId);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams());
}
final String resourceName = myContext.getResourceDefinition(theType).getName();
IClientResponseHandler binding = new IClientResponseHandler() {
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
MethodOutcome response = BaseOutcomeReturningMethodBinding.process2xxResponse(myContext, resourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
return response;
}
};
MethodOutcome resp = (MethodOutcome) invokeClient(binding, invocation);
return resp;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
* For now, this is a part of the internal API of HAPI - Use with caution as
* this method may change!
*/
public void setLastRequest(HttpRequestBase theLastRequest) {
myLastRequest = theLastRequest;
@ -93,19 +125,17 @@ public class GenericClient extends BaseClient implements IGenericClient {
return myContext.getResourceDefinition(theType).getName();
}
@Override
public <T extends IResource> T vread(final Class<T> theType, IdDt theId, IdDt theVersionId) {
GetClientInvocation invocation = ReadMethodBinding.createVReadInvocation(theId, theVersionId, toResourceName(theType));
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams());
}
IClientResponseHandler binding = new IClientResponseHandler() {
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
EncodingUtil respType = EncodingUtil.forContentType(theResponseMimeType);
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
IParser parser = respType.newParser(myContext);
return parser.parseResource(theType, theResponseReader);
}
@ -116,7 +146,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp;
}
@Override
public <T extends IResource> Bundle search(final Class<T> theType, Map<String, List<IQueryParameterType>> theParams) {
LinkedHashMap<String, List<String>> params = new LinkedHashMap<String, List<String>>();
@ -127,17 +156,16 @@ public class GenericClient extends BaseClient implements IGenericClient {
valueList.add(nextValue.getValueAsQueryToken());
}
}
GetClientInvocation invocation = SearchMethodBinding.createSearchInvocation(toResourceName(theType), params);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase());
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams());
}
IClientResponseHandler binding = new IClientResponseHandler() {
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
EncodingUtil respType = EncodingUtil.forContentType(theResponseMimeType);
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
IParser parser = respType.newParser(myContext);
return parser.parseBundle(theType, theResponseReader);
}
@ -146,5 +174,112 @@ public class GenericClient extends BaseClient implements IGenericClient {
Bundle resp = (Bundle) invokeClient(binding, invocation);
return resp;
}
public MethodOutcome create(IResource theResource) {
BaseClientInvocation invocation = CreateMethodBinding.createCreateInvocation(theResource, myContext);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams());
}
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
final String resourceName = def.getName();
IClientResponseHandler binding = new IClientResponseHandler() {
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
MethodOutcome response = BaseOutcomeReturningMethodBinding.process2xxResponse(myContext, resourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
return response;
}
};
MethodOutcome resp = (MethodOutcome) invokeClient(binding, invocation);
return resp;
}
@Override
public MethodOutcome update(IdDt theIdDt, IResource theResource) {
BaseClientInvocation invocation = UpdateMethodBinding.createUpdateInvocation(theResource, theIdDt, null, myContext);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams());
}
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
final String resourceName = def.getName();
IClientResponseHandler binding = new IClientResponseHandler() {
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
MethodOutcome response = BaseOutcomeReturningMethodBinding.process2xxResponse(myContext, resourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
return response;
}
};
MethodOutcome resp = (MethodOutcome) invokeClient(binding, invocation);
return resp;
}
@Override
public MethodOutcome validate(IResource theResource) {
BaseClientInvocation invocation = ValidateMethodBinding.createValidateInvocation(theResource, null, myContext);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams());
}
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
final String resourceName = def.getName();
IClientResponseHandler binding = new IClientResponseHandler() {
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
MethodOutcome response = BaseOutcomeReturningMethodBinding.process2xxResponse(myContext, resourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
return response;
}
};
MethodOutcome resp = (MethodOutcome) invokeClient(binding, invocation);
return resp;
}
@Override
public <T extends IResource> Bundle history(final Class<T> theType, IdDt theIdDt) {
GetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(toResourceName(theType), theIdDt);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams());
}
IClientResponseHandler binding = new IClientResponseHandler() {
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
IParser parser = respType.newParser(myContext);
return parser.parseBundle(theType, theResponseReader);
}
};
Bundle resp = (Bundle) invokeClient(binding, invocation);
return resp;
}
@Override
public Conformance conformance() {
GetClientInvocation invocation = ConformanceMethodBinding.createConformanceInvocation();
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams());
}
IClientResponseHandler binding = new IClientResponseHandler() {
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
IParser parser = respType.newParser(myContext);
return parser.parseResource(Conformance.class, theResponseReader);
}
};
Conformance resp = (Conformance) invokeClient(binding, invocation);
return resp;
}
}

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.rest.client;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@ -44,13 +44,13 @@ public class GetClientInvocation extends BaseClientInvocation {
}
public GetClientInvocation(String theUrlPath) {
myParameters = Collections.emptyMap();
myParameters = new HashMap<String, List<String>>();
myUrlPath = theUrlPath;
}
public GetClientInvocation(String... theUrlFragments) {
myParameters = Collections.emptyMap();
myParameters = new HashMap<String, List<String>>();
myUrlPath = StringUtils.join(theUrlFragments, '/');
}
@ -63,7 +63,7 @@ public class GetClientInvocation extends BaseClientInvocation {
}
@Override
public HttpRequestBase asHttpRequest(String theUrlBase) {
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams) {
StringBuilder b = new StringBuilder();
b.append(theUrlBase);
if (!theUrlBase.endsWith("/")) {
@ -92,6 +92,9 @@ public class GetClientInvocation extends BaseClientInvocation {
}
}
}
appendExtraParamsWithQuestionMark(theExtraParams, b, first);
return new HttpGet(b.toString());
}

View File

@ -26,7 +26,9 @@ import java.util.Map;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
public interface IGenericClient {
@ -57,4 +59,43 @@ public interface IGenericClient {
*/
<T extends IResource> Bundle search(Class<T> theType, Map<String, List<IQueryParameterType>> theParams);
/**
* Implementation of the "instance update" method.
*
* @param theId The ID to update
* @param theResource The new resource body
* @return An outcome containing the results and possibly the new version ID
*/
MethodOutcome update(IdDt theIdDt, IResource theResource);
/**
* Implementation of the "type validate" method.
*
* @param theId The ID to validate
* @param theResource The resource to validate
* @return An outcome containing any validation issues
*/
MethodOutcome validate(IResource theResource);
/**
* Implementation of the "delete instance" method.
* @param theType The type of resource to delete
* @param theId the ID of the resource to delete
* @return An outcome
*/
MethodOutcome delete(Class<? extends IResource> theType, IdDt theId);
/**
* Implementation of the "history instance" method.
* @param theType The type of resource to return the history for
* @param theId the ID of the resource to return the history for
* @return An outcome
*/
<T extends IResource> Bundle history(Class<T> theType, IdDt theIdDt);
/**
* Retrieves and returns the server conformance statement
*/
Conformance conformance();
}

View File

@ -58,7 +58,7 @@ import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
@ -277,11 +277,11 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
// return sm;
}
public static EncodingUtil determineResponseEncoding(HttpServletRequest theRequest, Map<String, String[]> theParams) {
public static EncodingEnum determineResponseEncoding(HttpServletRequest theRequest, Map<String, String[]> theParams) {
String[] format = theParams.remove(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
EncodingUtil retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
if (retVal != null) {
return retVal;
}
@ -291,13 +291,13 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
Enumeration<String> acceptValues = theRequest.getHeaders("Accept");
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
EncodingUtil retVal = Constants.FORMAT_VAL_TO_ENCODING.get(acceptValues.nextElement());
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(acceptValues.nextElement());
if (retVal != null) {
return retVal;
}
}
}
return EncodingUtil.XML;
return EncodingEnum.XML;
}
public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) {
@ -357,4 +357,16 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
}
}
protected static boolean prettyPrintResponse(Request theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] pretty = requestParams.remove(Constants.PARAM_PRETTY);
boolean prettyPrint = false;
if (pretty != null && pretty.length > 0) {
if (Constants.PARAM_PRETTY_VALUE_TRUE.equals(pretty[0])) {
prettyPrint = true;
}
}
return prettyPrint;
}
}

View File

@ -46,7 +46,7 @@ import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -58,7 +58,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceVersionNotSpecifiedException;
import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
private boolean myReturnVoid;
@ -83,40 +83,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
if (myReturnVoid) {
return null;
}
List<String> locationHeaders = theHeaders.get("location");
MethodOutcome retVal = new MethodOutcome();
if (locationHeaders != null && locationHeaders.size() > 0) {
String locationHeader = locationHeaders.get(0);
parseContentLocation(retVal, locationHeader);
}
if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) {
EncodingUtil ct = EncodingUtil.forContentType(theResponseMimeType);
if (ct != null) {
PushbackReader reader = new PushbackReader(theResponseReader);
try {
int firstByte = reader.read();
if (firstByte == -1) {
ourLog.debug("No content in response, not going to read");
reader = null;
} else {
reader.unread(firstByte);
}
} catch (IOException e) {
ourLog.debug("No content in response, not going to read", e);
reader = null;
}
if (reader != null) {
IParser parser = ct.newParser(getContext());
OperationOutcome outcome = parser.parseResource(OperationOutcome.class, reader);
retVal.setOperationOutcome(outcome);
}
} else {
ourLog.debug("Ignoring response content of type: {}", theResponseMimeType);
}
}
MethodOutcome retVal = process2xxResponse(getContext(), getResourceName(), theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
return retVal;
case Constants.STATUS_HTTP_400_BAD_REQUEST:
throw new InvalidRequestException("Server responded with: " + IOUtils.toString(theResponseReader));
@ -138,30 +105,86 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
}
protected void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingUtil theEncoding, HttpServletResponse theResponse) throws IOException {
theResponse.setStatus(theE.getStatusCode());
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
EncodingEnum encoding = BaseMethodBinding.determineResponseEncoding(theRequest.getServletRequest(), theRequest.getParameters());
IParser parser = encoding.newParser(getContext());
IResource resource;
if (requestContainsResource()) {
resource = parser.parseResource(theRequest.getInputReader());
} else {
resource = null;
}
Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) {
IParameter param = getParameters().get(i);
if (param == null) {
continue;
}
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, resource);
}
addParametersForServerRequest(theRequest, params);
MethodOutcome response;
try {
response = (MethodOutcome) invokeServerMethod(getProvider(), params);
} catch (InternalErrorException e) {
ourLog.error("Internal error during method invocation", e);
streamOperationOutcome(e, theServer, encoding, theResponse, theRequest);
return;
} catch (BaseServerResponseException e) {
ourLog.info("Exception during method invocation: " + e.getMessage());
streamOperationOutcome(e, theServer, encoding, theResponse, theRequest);
return;
}
if (getResourceOperationType() == RestfulOperationTypeEnum.CREATE) {
if (response == null) {
throw new InternalErrorException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null, which is not allowed for create operation");
}
theResponse.setStatus(Constants.STATUS_HTTP_201_CREATED);
addLocationHeader(theRequest, theResponse, response);
} else if (response == null) {
if (isReturnVoid() == false) {
throw new InternalErrorException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null");
}
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
} else {
if (response.getOperationOutcome() == null) {
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
} else {
theResponse.setStatus(Constants.STATUS_HTTP_200_OK);
}
if (getResourceOperationType() == RestfulOperationTypeEnum.UPDATE) {
addLocationHeader(theRequest, theResponse, response);
}
}
theServer.addHeadersToResponse(theResponse);
if (theE.getOperationOutcome() != null) {
theResponse.setContentType(theEncoding.getResourceContentType());
IParser parser = theEncoding.newParser(theServer.getFhirContext());
if (response != null && response.getOperationOutcome() != null) {
theResponse.setContentType(encoding.getResourceContentType());
Writer writer = theResponse.getWriter();
parser.setPrettyPrint(prettyPrintResponse(theRequest));
try {
parser.encodeResourceToWriter(theE.getOperationOutcome(), writer);
parser.encodeResourceToWriter(response.getOperationOutcome(), writer);
} finally {
writer.close();
}
} else {
theResponse.setContentType(Constants.CT_TEXT);
Writer writer = theResponse.getWriter();
try {
writer.append(theE.getMessage());
} finally {
writer.close();
}
writer.close();
}
// getMethod().in
}
public boolean isReturnVoid() {
return myReturnVoid;
}
/*
@ -209,111 +232,6 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
* writer.close(); } // getMethod().in }
*/
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
EncodingUtil encoding = BaseMethodBinding.determineResponseEncoding(theRequest.getServletRequest(), theRequest.getParameters());
IParser parser = encoding.newParser(getContext());
IResource resource;
if (requestContainsResource()) {
resource = parser.parseResource(theRequest.getInputReader());
} else {
resource = null;
}
Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) {
IParameter param = getParameters().get(i);
if (param == null) {
continue;
}
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, resource);
}
addParametersForServerRequest(theRequest, params);
MethodOutcome response;
try {
response = (MethodOutcome) invokeServerMethod(getProvider(), params);
} catch (InternalErrorException e) {
ourLog.error("Internal error during method invocation", e);
streamOperationOutcome(e, theServer, encoding, theResponse);
return;
} catch (BaseServerResponseException e) {
ourLog.info("Exception during method invocation: "+e.getMessage());
streamOperationOutcome(e, theServer, encoding, theResponse);
return;
}
if (getResourceOperationType() == RestfulOperationTypeEnum.CREATE) {
if (response == null) {
throw new InternalErrorException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null, which is not allowed for create operation");
}
theResponse.setStatus(Constants.STATUS_HTTP_201_CREATED);
addLocationHeader(theRequest, theResponse, response);
} else if (response == null) {
if (isReturnVoid() == false) {
throw new InternalErrorException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null");
}
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
} else {
if (response.getOperationOutcome() == null) {
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
} else {
theResponse.setStatus(Constants.STATUS_HTTP_200_OK);
}
if (getResourceOperationType() == RestfulOperationTypeEnum.UPDATE) {
addLocationHeader(theRequest, theResponse, response);
}
}
theServer.addHeadersToResponse(theResponse);
if (response != null && response.getOperationOutcome() != null) {
theResponse.setContentType(encoding.getResourceContentType());
Writer writer = theResponse.getWriter();
try {
parser.encodeResourceToWriter(response.getOperationOutcome(), writer);
} finally {
writer.close();
}
} else {
theResponse.setContentType(Constants.CT_TEXT);
Writer writer = theResponse.getWriter();
writer.close();
}
// getMethod().in
}
private void addLocationHeader(Request theRequest, HttpServletResponse theResponse, MethodOutcome response) {
StringBuilder b = new StringBuilder();
b.append(theRequest.getFhirServerBase());
b.append('/');
b.append(getResourceName());
b.append('/');
b.append(response.getId().getValue());
if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) {
b.append("/_history/");
b.append(response.getVersionId().getValue());
}
theResponse.addHeader("Location", b.toString());
}
/**
* Subclasses may override if the incoming request should not contain a
* resource
*/
protected boolean requestContainsResource() {
return true;
}
protected abstract void addParametersForServerRequest(Request theRequest, Object[] theParams);
public boolean isReturnVoid() {
return myReturnVoid;
}
@Override
public boolean matches(Request theRequest) {
Set<RequestType> allowableRequestTypes = provideAllowableRequestTypes();
@ -333,12 +251,21 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
return true;
}
/**
* For servers, this method will match only incoming requests that match the
* given operation, or which have no operation in the URL if this method
* returns null.
*/
protected abstract String getMatchingOperation();
private void addLocationHeader(Request theRequest, HttpServletResponse theResponse, MethodOutcome response) {
StringBuilder b = new StringBuilder();
b.append(theRequest.getFhirServerBase());
b.append('/');
b.append(getResourceName());
b.append('/');
b.append(response.getId().getValue());
if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) {
b.append("/_history/");
b.append(response.getVersionId().getValue());
}
theResponse.addHeader("Location", b.toString());
}
protected abstract void addParametersForServerRequest(Request theRequest, Object[] theParams);
/**
* Subclasses may override to allow a void method return type, which is
@ -348,10 +275,91 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
return false;
}
protected abstract BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName);
protected abstract BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource);
protected void parseContentLocation(MethodOutcome theOutcomeToPopulate, String theLocationHeader) {
String resourceNamePart = "/" + getResourceName() + "/";
/**
* For servers, this method will match only incoming requests that match the
* given operation, or which have no operation in the URL if this method
* returns null.
*/
protected abstract String getMatchingOperation();
protected abstract Set<RequestType> provideAllowableRequestTypes();
/**
* Subclasses may override if the incoming request should not contain a
* resource
*/
protected boolean requestContainsResource() {
return true;
}
protected void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingEnum theEncoding, HttpServletResponse theResponse, Request theRequest) throws IOException {
theResponse.setStatus(theE.getStatusCode());
theServer.addHeadersToResponse(theResponse);
if (theE.getOperationOutcome() != null) {
theResponse.setContentType(theEncoding.getResourceContentType());
IParser parser = theEncoding.newParser(theServer.getFhirContext());
parser.setPrettyPrint(prettyPrintResponse(theRequest));
Writer writer = theResponse.getWriter();
try {
parser.encodeResourceToWriter(theE.getOperationOutcome(), writer);
} finally {
writer.close();
}
} else {
theResponse.setContentType(Constants.CT_TEXT);
Writer writer = theResponse.getWriter();
try {
writer.append(theE.getMessage());
} finally {
writer.close();
}
}
}
public static MethodOutcome process2xxResponse(FhirContext theContext, String theResourceName, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map<String, List<String>> theHeaders) {
List<String> locationHeaders = theHeaders.get("location");
MethodOutcome retVal = new MethodOutcome();
if (locationHeaders != null && locationHeaders.size() > 0) {
String locationHeader = locationHeaders.get(0);
parseContentLocation(retVal, theResourceName, locationHeader);
}
if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) {
EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType);
if (ct != null) {
PushbackReader reader = new PushbackReader(theResponseReader);
try {
int firstByte = reader.read();
if (firstByte == -1) {
ourLog.debug("No content in response, not going to read");
reader = null;
} else {
reader.unread(firstByte);
}
} catch (IOException e) {
ourLog.debug("No content in response, not going to read", e);
reader = null;
}
if (reader != null) {
IParser parser = ct.newParser(theContext);
OperationOutcome outcome = parser.parseResource(OperationOutcome.class, reader);
retVal.setOperationOutcome(outcome);
}
} else {
ourLog.debug("Ignoring response content of type: {}", theResponseMimeType);
}
}
return retVal;
}
protected static void parseContentLocation(MethodOutcome theOutcomeToPopulate, String theResourceName, String theLocationHeader) {
String resourceNamePart = "/" + theResourceName + "/";
int resourceIndex = theLocationHeader.lastIndexOf(resourceNamePart);
if (resourceIndex > -1) {
int idIndexStart = resourceIndex + resourceNamePart.length();
@ -369,6 +377,4 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
}
}
protected abstract Set<RequestType> provideAllowableRequestTypes();
}

View File

@ -20,26 +20,15 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ResourceParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOutcomeReturningMethodBinding {
@ -91,10 +80,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
throw new NullPointerException("Resource can not be null");
}
RuntimeResourceDefinition def = getContext().getResourceDefinition(resource);
String resourceName = def.getName();
return createClientInvocation(theArgs, resource, resourceName);
return createClientInvocation(theArgs, resource);
}
}

View File

@ -56,7 +56,7 @@ import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServer.NarrativeModeEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
@ -179,16 +179,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
// Pretty print
Map<String, String[]> requestParams = theRequest.getParameters();
String[] pretty = requestParams.remove(Constants.PARAM_PRETTY);
boolean prettyPrint = false;
if (pretty != null && pretty.length > 0) {
if ("true".equals(pretty[0])) {
prettyPrint = true;
}
}
boolean prettyPrint = prettyPrintResponse(theRequest);
// Narrative mode
Map<String, String[]> requestParams = theRequest.getParameters();
String[] narrative = requestParams.remove(Constants.PARAM_NARRATIVE);
NarrativeModeEnum narrativeMode = null;
if (narrative != null && narrative.length > 0) {
@ -199,7 +193,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
}
// Determine response encoding
EncodingUtil responseEncoding = determineResponseEncoding(theRequest.getServletRequest(), requestParams);
EncodingEnum responseEncoding = determineResponseEncoding(theRequest.getServletRequest(), requestParams);
// Is this request coming from a browser
String uaHeader = theRequest.getServletRequest().getHeader("user-agent");
@ -233,7 +227,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
}
}
private IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
protected static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
Object retValObj = theResourceMetadata.get(theKey);
if (retValObj == null) {
return null;
@ -271,7 +266,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
+ InstantDt.class.getCanonicalName());
}
private IParser getNewParser(EncodingUtil theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) {
private IParser getNewParser(EncodingEnum theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) {
IParser parser;
switch (theResponseEncoding) {
case JSON:
@ -285,7 +280,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS);
}
private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingUtil theResponseEncoding, String theServerBase,
private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingEnum theResponseEncoding, String theServerBase,
String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
assert !theServerBase.endsWith("/");
@ -363,7 +358,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
b.append('?').append(Constants.PARAM_PRETTY).append("=true");
haveQ = true;
}
if (theResponseEncoding == EncodingUtil.JSON) {
if (theResponseEncoding == EncodingEnum.JSON) {
if (!haveQ) {
b.append('?');
haveQ = true;
@ -396,7 +391,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
}
}
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint,
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
theHttpResponse.setStatus(200);

View File

@ -53,6 +53,10 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
@Override
public GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
return createConformanceInvocation();
}
public static GetClientInvocation createConformanceInvocation() {
return new GetClientInvocation("metadata");
}

View File

@ -25,6 +25,7 @@ import java.util.Collections;
import java.util.Set;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
@ -55,11 +56,20 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
}
@Override
protected BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName) {
protected BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource) {
FhirContext context = getContext();
return createCreateInvocation(resource, context);
}
public static BaseClientInvocation createCreateInvocation(IResource resource, FhirContext context) {
RuntimeResourceDefinition def = context.getResourceDefinition(resource);
String resourceName = def.getName();
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(resourceName);
return new PostClientInvocation(getContext(), resource, urlExtension.toString());
return new PostClientInvocation(context, resource, urlExtension.toString());
}
@Override

View File

@ -105,11 +105,11 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
}
@Override
protected BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName) {
protected BaseClientInvocation createClientInvocation(Object[] theArgs, IResource theResource) {
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(resourceName);
urlExtension.append(getContext().getResourceDefinition(theResource).getName());
return new PostClientInvocation(getContext(), resource, urlExtension.toString());
return new PostClientInvocation(getContext(), theResource, urlExtension.toString());
}
@Override
@ -129,23 +129,22 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
if (idDt == null) {
throw new NullPointerException("ID can not be null");
}
String resourceName = getResourceName();
DeleteClientInvocation retVal = createDeleteInvocation(resourceName, idDt);
return retVal;
}
public static DeleteClientInvocation createDeleteInvocation(String theResourceName, IdDt idDt) {
String id = idDt.getValue();
DeleteClientInvocation retVal = new DeleteClientInvocation(getResourceName(), id);
DeleteClientInvocation retVal = new DeleteClientInvocation(theResourceName, id);
return retVal;
}
@Override
protected void addParametersForServerRequest(Request theRequest, Object[] theParams) {
String url = theRequest.getCompleteUrl();
int resNameIdx = url.indexOf(getResourceName());
String id = url.substring(resNameIdx+getResourceName().length() + 1);
if (id.contains("/")) {
throw new InvalidRequestException("Invalid request path for a DELETE operation: "+theRequest.getCompleteUrl());
}
theParams[myIdParameterIndex] = new IdDt(id);
theParams[myIdParameterIndex] = theRequest.getId();
}

View File

@ -23,13 +23,13 @@ package ca.uhn.fhir.rest.method;
import static org.apache.commons.lang3.StringUtils.*;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import org.apache.commons.lang3.ObjectUtils;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
@ -103,32 +103,42 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
@Override
public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
IdDt id = null;
String resourceName = myResourceName;
if (myIdParamIndex != null) {
id = (IdDt) theArgs[myIdParamIndex];
if (id == null || isBlank(id.getValue())) {
throw new NullPointerException("ID can not be null");
}
}
GetClientInvocation retVal = createHistoryInvocation(resourceName, id);
if (theArgs != null) {
for (int idx = 0; idx < theArgs.length; idx++) {
IParameter nextParam = getParameters().get(idx);
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], retVal.getParameters());
}
}
return retVal;
}
public static GetClientInvocation createHistoryInvocation(String theResourceName, IdDt theId) {
StringBuilder b = new StringBuilder();
if (myResourceName != null) {
b.append(myResourceName);
if (myIdParamIndex != null) {
IdDt id = (IdDt) theArgs[myIdParamIndex];
if (id == null || isBlank(id.getValue())) {
throw new NullPointerException("ID can not be null");
}
if (theResourceName != null) {
b.append(theResourceName);
if (theId != null) {
b.append('/');
b.append(id.getValue());
b.append(theId.getValue());
}
}
if (b.length() > 0) {
b.append('/');
}
b.append(Constants.PARAM_HISTORY);
LinkedHashMap<String, List<String>> queryStringArgs=new LinkedHashMap<String, List<String>>();
if (theArgs != null) {
for (int idx = 0; idx < theArgs.length; idx++) {
IParameter nextParam = getParameters().get(idx);
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], queryStringArgs);
}
}
return new GetClientInvocation(queryStringArgs, b.toString());
GetClientInvocation retVal = new GetClientInvocation(b.toString());
return retVal;
}
@Override
@ -139,7 +149,20 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
Object response = invokeServerMethod(theResourceProvider, theMethodParams);
return toResourceList(response);
List<IResource> resources = toResourceList(response);
int index=0;
for (IResource nextResource : resources) {
if (nextResource.getId() == null || nextResource.getId().isEmpty()) {
throw new InternalErrorException("Server provided resource at index " + index + " with no ID set (using IResource#setId(IdDt))");
}
IdDt versionId = getIdFromMetadataOrNullIfNone(nextResource.getResourceMetadata(),ResourceMetadataKeyEnum.VERSION_ID);
if (versionId == null||versionId.isEmpty()) {
throw new InternalErrorException("Server provided resource at index " + index + " with no Version ID set (using IResource#Resource.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, Object))");
}
index++;
}
return resources;
}
// ObjectUtils.equals is replaced by a JDK7 method..

View File

@ -43,7 +43,7 @@ import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
private Integer myIdParameterIndex;
@ -79,9 +79,9 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
String locationHeader = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_LOCATION);
if (isNotBlank(locationHeader)) {
MethodOutcome mo = new MethodOutcome();
parseContentLocation(mo, locationHeader);
parseContentLocation(mo, getResourceName(), locationHeader);
if (mo.getId() == null || mo.getId().isEmpty()) {
throw new InvalidRequestException("Invalid Content-Location header for resource " + getResourceName()+ ": " + locationHeader);
throw new InvalidRequestException("Invalid Content-Location header for resource " + getResourceName() + ": " + locationHeader);
}
if (mo.getVersionId() != null && mo.getVersionId().isEmpty() == false) {
theRequest.setVersion(mo.getVersionId());
@ -99,34 +99,43 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
}
@Override
protected BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName) {
protected BaseClientInvocation createClientInvocation(Object[] theArgs, IResource theResource) {
IdDt idDt = (IdDt) theArgs[myIdParameterIndex];
if (idDt == null) {
throw new NullPointerException("ID can not be null");
}
String id = idDt.getValue();
IdDt versionIdDt = null;
if (myVersionIdParameterIndex != null) {
versionIdDt = (IdDt) theArgs[myVersionIdParameterIndex];
}
FhirContext context = getContext();
PutClientInvocation retVal = createUpdateInvocation(theResource, idDt, versionIdDt, context);
return retVal;
}
public static PutClientInvocation createUpdateInvocation(IResource theResource, IdDt idDt, IdDt versionIdDt, FhirContext context) {
String id = idDt.getValue();
String resourceName = context.getResourceDefinition(theResource).getName();
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(resourceName);
urlExtension.append('/');
urlExtension.append(id);
PutClientInvocation retVal = new PutClientInvocation(getContext(), resource, urlExtension.toString());
if (myVersionIdParameterIndex != null) {
IdDt versionIdDt = (IdDt) theArgs[myVersionIdParameterIndex];
if (versionIdDt != null) {
String versionId = versionIdDt.getValue();
if (StringUtils.isNotBlank(versionId)) {
StringBuilder b = new StringBuilder();
b.append('/');
b.append(urlExtension);
b.append("/_history/");
b.append(versionId);
retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, b.toString());
}
PutClientInvocation retVal = new PutClientInvocation(context, theResource, urlExtension.toString());
if (versionIdDt != null) {
String versionId = versionIdDt.getValue();
if (StringUtils.isNotBlank(versionId)) {
StringBuilder b = new StringBuilder();
b.append('/');
b.append(urlExtension);
b.append("/_history/");
b.append(versionId);
retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, b.toString());
}
}
return retVal;
}
@ -149,7 +158,6 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
return Collections.singleton(RequestType.PUT);
}
@Override
protected String getMatchingOperation() {
return null;

View File

@ -31,11 +31,11 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.PutClientInvocation;
import ca.uhn.fhir.rest.client.PostClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.Constants;
class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
public class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
private Integer myIdParameterIndex;
@ -63,22 +63,31 @@ class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourc
}
@Override
protected BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName) {
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(resourceName);
urlExtension.append(Constants.PARAM_VALIDATE);
protected BaseClientInvocation createClientInvocation(Object[] theArgs, IResource theResource) {
FhirContext context = getContext();
IdDt idDt=null;
if (myIdParameterIndex != null) {
IdDt idDt = (IdDt) theArgs[myIdParameterIndex];
if (idDt != null && idDt.isEmpty() == false) {
String id = idDt.getValue();
urlExtension.append('/');
urlExtension.append(id);
}
idDt = (IdDt) theArgs[myIdParameterIndex];
}
PutClientInvocation retVal = new PutClientInvocation(getContext(), resource, urlExtension.toString());
PostClientInvocation retVal = createValidateInvocation(theResource, idDt, context);
return retVal;
}
public static PostClientInvocation createValidateInvocation(IResource theResource, IdDt theId, FhirContext theContext) {
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(theContext.getResourceDefinition(theResource).getName());
urlExtension.append('/');
urlExtension.append(Constants.PARAM_VALIDATE);
if (theId != null && theId.isEmpty() == false) {
String id = theId.getValue();
urlExtension.append('/');
urlExtension.append(id);
}
// TODO: is post correct here?
PostClientInvocation retVal = new PostClientInvocation(theContext, theResource, urlExtension.toString());
return retVal;
}
@ -90,6 +99,7 @@ class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourc
@Override
protected Set<RequestType> provideAllowableRequestTypes() {
// TODO: is post correct here?
return Collections.singleton(RequestType.POST);
}

View File

@ -75,7 +75,7 @@ public class SinceParameter implements IParameter {
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Since.class.getName() + " but can not be of collection type");
}
if (!ParameterUtil.getBindableInstantTypes().contains(theParameterType)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Since.class.getName() + " but cis an invalid type, must be one of: " + ParameterUtil.getBindableInstantTypes());
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Since.class.getName() + " but is an invalid type, must be one of: " + ParameterUtil.getBindableInstantTypes());
}
myType = theParameterType;
}

View File

@ -0,0 +1,59 @@
package ca.uhn.fhir.rest.param;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.rest.annotation.Sort;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class SortParameter implements IParameter {
@Override
public void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
SortSpec ss = (SortSpec) theSourceClientArgument;
if (ss ==null) {
return;
}
String name;
if (ss.getOrder()==null) {
name = Constants.PARAM_SORT;
}else if (ss.getOrder() == SortOrderEnum.ASC) {
name = Constants.PARAM_SORT_ASC;
}else {
name = Constants.PARAM_SORT_DESC;
}
if (ss.getFieldName() != null) {
if (!theTargetQueryArguments.containsKey(name)) {
// TODO: implement
}
}
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
// TODO Auto-generated method stub
return null;
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
if (theOuterCollectionType != null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Sort.class.getName() + " but can not be of collection type");
}
if (!ParameterUtil.getBindableInstantTypes().contains(theParameterType)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Sort.class.getName() + " but is an invalid type, must be: " + SortSpec.class.getCanonicalName());
}
}
}

View File

@ -35,7 +35,7 @@ public class Constants {
public static final String CT_ATOM_XML = "application/atom+xml";
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 Map<String, EncodingEnum> FORMAT_VAL_TO_ENCODING;
public static final String CT_XML = "application/xml";
public static final String CT_JSON = "application/json";
public static final String CT_HTML = "text/html";
@ -62,9 +62,13 @@ public class Constants {
public static final String PARAM_VALIDATE = "_validate";
public static final int STATUS_HTTP_401_CLIENT_UNAUTHORIZED = 401;
public static final int STATUS_HTTP_500_INTERNAL_ERROR = 500;
public static final String PARAM_PRETTY_VALUE_TRUE = "true";
public static final String PARAM_SORT = "_sort";
public static final String PARAM_SORT_ASC = "_sort:asc";
public static final String PARAM_SORT_DESC = "_sort:desc";
static {
Map<String, EncodingUtil> valToEncoding = new HashMap<String, EncodingUtil>();
Map<String, EncodingEnum> valToEncoding = new HashMap<String, EncodingEnum>();
HashSet<String> valXml = new HashSet<String>();
valXml.add(CT_FHIR_XML);
@ -72,7 +76,7 @@ public class Constants {
valXml.add("xml");
FORMAT_VAL_XML = Collections.unmodifiableSet(valXml);
for (String string : valXml) {
valToEncoding.put(string, EncodingUtil.XML);
valToEncoding.put(string, EncodingEnum.XML);
}
HashSet<String> valJson = new HashSet<String>();
@ -81,7 +85,7 @@ public class Constants {
valJson.add("json");
FORMAT_VAL_JSON = Collections.unmodifiableSet(valJson);
for (String string : valJson) {
valToEncoding.put(string, EncodingUtil.JSON);
valToEncoding.put(string, EncodingEnum.JSON);
}
FORMAT_VAL_TO_ENCODING=Collections.unmodifiableMap(valToEncoding);

View File

@ -25,7 +25,7 @@ import java.util.HashMap;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
public enum EncodingUtil {
public enum EncodingEnum {
XML(Constants.CT_FHIR_XML, Constants.CT_ATOM_XML, Constants.CT_XML) {
@Override
@ -43,11 +43,11 @@ public enum EncodingUtil {
;
private static HashMap<String, EncodingUtil> ourContentTypeToEncoding;
private static HashMap<String, EncodingEnum> ourContentTypeToEncoding;
static {
ourContentTypeToEncoding = new HashMap<String, EncodingUtil>();
for (EncodingUtil next: values()) {
ourContentTypeToEncoding = new HashMap<String, EncodingEnum>();
for (EncodingEnum next: values()) {
ourContentTypeToEncoding.put(next.getBundleContentType(), next);
ourContentTypeToEncoding.put(next.getResourceContentType(), next);
ourContentTypeToEncoding.put(next.getBrowserFriendlyBundleContentType(), next);
@ -58,7 +58,7 @@ public enum EncodingUtil {
private String myBundleContentType;
private String myBrowserFriendlyContentType;
EncodingUtil(String theResourceContentType, String theBundleContentType, String theBrowserFriendlyContentType) {
EncodingEnum(String theResourceContentType, String theBundleContentType, String theBrowserFriendlyContentType) {
myResourceContentType = theResourceContentType;
myBundleContentType = theBundleContentType;
myBrowserFriendlyContentType = theBrowserFriendlyContentType;
@ -78,7 +78,7 @@ public enum EncodingUtil {
return myBrowserFriendlyContentType;
}
public static EncodingUtil forContentType(String theContentType) {
public static EncodingEnum forContentType(String theContentType) {
return ourContentTypeToEncoding.get(theContentType);
}

View File

@ -81,7 +81,11 @@ public class RestfulServer extends HttpServlet {
* Constructor
*/
public RestfulServer() {
myFhirContext = new FhirContext();
this(new FhirContext());
}
public RestfulServer(FhirContext theCtx) {
myFhirContext = theCtx;
myServerConformanceProvider = new ServerConformanceProvider(this);
}

View File

@ -41,6 +41,10 @@ public class ResourceNotFoundException extends BaseServerResponseException {
super(STATUS_CODE, "Resource of type " + theClass.getSimpleName() + " with ID " + thePatientId + " is not known");
}
public ResourceNotFoundException(Class<? extends IResource> theClass, IdDt thePatientId) {
super(STATUS_CODE, "Resource of type " + theClass.getSimpleName() + " with ID " + thePatientId + " is not known");
}
public ResourceNotFoundException(String theMessage) {
super(STATUS_CODE, theMessage);
}

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.server.provider;
* #L%
*/
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -31,6 +33,7 @@ import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.Conformance.Rest;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResourceOperation;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResourceSearchParam;
import ca.uhn.fhir.model.dstu.valueset.RestfulConformanceModeEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
@ -40,10 +43,8 @@ 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.param.BaseQueryParameter;
import ca.uhn.fhir.rest.param.SearchParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.ResourceBinding;
@ -155,7 +156,25 @@ public class ServerConformanceProvider {
}
}
Collections.sort(resource.getOperation(), new Comparator<RestResourceOperation>() {
@Override
public int compare(RestResourceOperation theO1, RestResourceOperation theO2) {
RestfulOperationTypeEnum o1 = theO1.getCode().getValueAsEnum();
RestfulOperationTypeEnum o2 = theO2.getCode().getValueAsEnum();
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return 1;
}
if (o2==null) {
return -1;
}
return o1.ordinal() - o2.ordinal();
}});
}
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.server.tester;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -35,8 +36,12 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.WriterOutputStream;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.thymeleaf.TemplateEngine;
@ -49,6 +54,7 @@ import org.thymeleaf.templateresolver.TemplateResolver;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.primitive.IdDt;
@ -57,21 +63,20 @@ import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.client.GenericClient;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
public class PublicTesterServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PublicTesterServlet.class);
private static final boolean DEBUGMODE = true;
private TemplateEngine myTemplateEngine;
private HashMap<String, String> myStaticResources;
private String myServerBase;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PublicTesterServlet.class);
private static final String PUBLIC_TESTER_RESULT_HTML = "/PublicTesterResult.html";
private static final long serialVersionUID = 1L;
private FhirContext myCtx;
private String myServerBase;
private HashMap<String, String> myStaticResources;
public void setServerBase(String theServerBase) {
myServerBase = theServerBase;
}
private TemplateEngine myTemplateEngine;
public PublicTesterServlet() {
myStaticResources = new HashMap<String, String>();
@ -91,125 +96,18 @@ public class PublicTesterServlet extends HttpServlet {
}
@Override
protected void doPost(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
if (DEBUGMODE) {
myTemplateEngine.getCacheManager().clearAllCaches();
}
public void init(ServletConfig theConfig) throws ServletException {
myTemplateEngine = new TemplateEngine();
TemplateResolver resolver = new TemplateResolver();
resolver.setResourceResolver(new ProfileResourceResolver());
myTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect();
myTemplateEngine.setDialect(dialect);
myTemplateEngine.initialize();
}
try {
GenericClient client = (GenericClient) myCtx.newRestfulGenericClient(myServerBase);
client.setKeepResponses(true);
String method = theReq.getParameter("method");
String requestUrl;
String action;
String resultStatus;
String resultBody;
String resultSyntaxHighlighterClass;
if ("read".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
client.read(def.getImplementingClass(), new IdDt(id));
}
if ("vread".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
String versionId = StringUtils.defaultString(theReq.getParameter("versionid"));
if (StringUtils.isBlank(versionId)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No Version ID specified");
}
client.vread(def.getImplementingClass(), new IdDt(id), new IdDt(versionId));
} else if ("searchType".equals(method)) {
Map<String, List<IQueryParameterType>> params = new HashMap<String, List<IQueryParameterType>>();
HashSet<String> hashSet = new HashSet<String>(theReq.getParameterMap().keySet());
String paramName = null;
IQueryParameterType paramValue = null;
while (hashSet.isEmpty() == false) {
String nextKey = hashSet.iterator().next();
String nextValue = theReq.getParameter(nextKey);
paramName=null;
paramValue=null;
if (nextKey.startsWith("param.token.")) {
int prefixLength = "param.token.".length();
paramName = nextKey.substring(prefixLength + 2);
String systemKey = "param.token."+"1." + paramName;
String valueKey = "param.token."+"2." + paramName;
String system = theReq.getParameter(systemKey);
String value = theReq.getParameter(valueKey);
paramValue = new IdentifierDt(system, value);
hashSet.remove(systemKey);
hashSet.remove(valueKey);
} else if (nextKey.startsWith("param.string.")) {
paramName = nextKey.substring("param.string.".length());
paramValue = new StringDt(nextValue);
}
if (paramName != null) {
if (params.containsKey(paramName) == false) {
params.put(paramName, new ArrayList<IQueryParameterType>());
}
params.get(paramName).add(paramValue);
}
hashSet.remove(nextKey);
}
RuntimeResourceDefinition def = getResourceType(theReq);
client.search(def.getImplementingClass(), params);
} else {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "Invalid method: " + method);
return;
}
HttpRequestBase lastRequest = client.getLastRequest();
requestUrl = lastRequest.getURI().toASCIIString();
action = client.getLastRequest().getMethod();
resultStatus = client.getLastResponse().getStatusLine().toString();
resultBody = client.getLastResponseBody();
ContentType ct = ContentType.get(client.getLastResponse().getEntity());
String mimeType = ct.getMimeType();
EncodingUtil ctEnum = EncodingUtil.forContentType(mimeType);
switch (ctEnum) {
case JSON:
resultSyntaxHighlighterClass = "brush: jscript";
break;
case XML:
resultSyntaxHighlighterClass = "brush: xml";
break;
default:
resultSyntaxHighlighterClass = "brush: plain";
break;
}
WebContext ctx = new WebContext(theReq, theResp, theReq.getServletContext(), theReq.getLocale());
ctx.setVariable("base", myServerBase);
ctx.setVariable("requestUrl", requestUrl);
ctx.setVariable("action", action);
ctx.setVariable("resultStatus", resultStatus);
ctx.setVariable("resultBody", StringEscapeUtils.escapeHtml4(resultBody));
ctx.setVariable("resultSyntaxHighlighterClass", resultSyntaxHighlighterClass);
myTemplateEngine.process(PUBLIC_TESTER_RESULT_HTML, ctx, theResp.getWriter());
} catch (Exception e) {
ourLog.error("Failure during processing", e);
theResp.sendError(500, e.toString());
}
public void setServerBase(String theServerBase) {
myServerBase = theServerBase;
}
private RuntimeResourceDefinition getResourceType(HttpServletRequest theReq) throws ServletException {
@ -221,6 +119,12 @@ public class PublicTesterServlet extends HttpServlet {
return def;
}
private void streamResponse(String theResourceName, String theContentType, HttpServletResponse theResp) throws IOException {
InputStream res = PublicTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/" + theResourceName);
theResp.setContentType(theContentType);
IOUtils.copy(res, theResp.getOutputStream());
}
@Override
protected void doGet(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
if (DEBUGMODE) {
@ -246,24 +150,289 @@ public class PublicTesterServlet extends HttpServlet {
}
@Override
protected void doPost(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
if (DEBUGMODE) {
myTemplateEngine.getCacheManager().clearAllCaches();
}
try {
GenericClient client = (GenericClient) myCtx.newRestfulGenericClient(myServerBase);
client.setKeepResponses(true);
String method = theReq.getParameter("method");
String prettyParam = theReq.getParameter("configPretty");
if ("on".equals(prettyParam)) {
client.setPrettyPrint(true);
}
if ("xml".equals(theReq.getParameter("configEncoding"))) {
client.setEncoding(EncodingEnum.XML);
} else if ("json".equals(theReq.getParameter("configEncoding"))) {
client.setEncoding(EncodingEnum.JSON);
}
String requestUrl;
String action;
String resultStatus;
String resultBody;
String resultSyntaxHighlighterClass;
boolean returnsResource;
try {
if ("conformance".equals(method)) {
returnsResource = true;
client.conformance();
} else if ("read".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
returnsResource = true;
client.read(def.getImplementingClass(), new IdDt(id));
} else if ("vread".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
String versionId = StringUtils.defaultString(theReq.getParameter("versionid"));
if (StringUtils.isBlank(versionId)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No Version ID specified");
}
returnsResource = true;
client.vread(def.getImplementingClass(), new IdDt(id), new IdDt(versionId));
} else if ("delete".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
returnsResource = false;
client.delete(def.getImplementingClass(), new IdDt(id));
} else if ("history-instance".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
returnsResource = false;
client.history(def.getImplementingClass(), new IdDt(id));
} else if ("create".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String resourceText = StringUtils.defaultString(theReq.getParameter("resource"));
if (StringUtils.isBlank(resourceText)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No resource content specified");
}
IResource resource;
if (client.getEncoding() == null || client.getEncoding() == EncodingEnum.XML) {
resource = myCtx.newXmlParser().parseResource(def.getImplementingClass(), resourceText);
} else {
resource = myCtx.newJsonParser().parseResource(def.getImplementingClass(), resourceText);
}
returnsResource = false;
client.create(resource);
} else if ("validate".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String resourceText = StringUtils.defaultString(theReq.getParameter("resource"));
if (StringUtils.isBlank(resourceText)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No resource content specified");
}
IResource resource;
if (client.getEncoding() == null || client.getEncoding() == EncodingEnum.XML) {
resource = myCtx.newXmlParser().parseResource(def.getImplementingClass(), resourceText);
} else {
resource = myCtx.newJsonParser().parseResource(def.getImplementingClass(), resourceText);
}
returnsResource = false;
client.validate(resource);
} else if ("update".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String resourceText = StringUtils.defaultString(theReq.getParameter("resource"));
if (StringUtils.isBlank(resourceText)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No resource content specified");
}
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
IResource resource;
if (client.getEncoding() == null || client.getEncoding() == EncodingEnum.XML) {
resource = myCtx.newXmlParser().parseResource(def.getImplementingClass(), resourceText);
} else {
resource = myCtx.newJsonParser().parseResource(def.getImplementingClass(), resourceText);
}
returnsResource = false;
client.update(new IdDt(id), resource);
} else if ("searchType".equals(method)) {
Map<String, List<IQueryParameterType>> params = new HashMap<String, List<IQueryParameterType>>();
HashSet<String> hashSet = new HashSet<String>(theReq.getParameterMap().keySet());
String paramName = null;
IQueryParameterType paramValue = null;
while (hashSet.isEmpty() == false) {
String nextKey = hashSet.iterator().next();
String nextValue = theReq.getParameter(nextKey);
paramName = null;
paramValue = null;
if (nextKey.startsWith("param.token.")) {
int prefixLength = "param.token.".length();
paramName = nextKey.substring(prefixLength + 2);
String systemKey = "param.token." + "1." + paramName;
String valueKey = "param.token." + "2." + paramName;
String system = theReq.getParameter(systemKey);
String value = theReq.getParameter(valueKey);
paramValue = new IdentifierDt(system, value);
hashSet.remove(systemKey);
hashSet.remove(valueKey);
} else if (nextKey.startsWith("param.string.")) {
paramName = nextKey.substring("param.string.".length());
paramValue = new StringDt(nextValue);
}
if (paramName != null) {
if (params.containsKey(paramName) == false) {
params.put(paramName, new ArrayList<IQueryParameterType>());
}
params.get(paramName).add(paramValue);
}
hashSet.remove(nextKey);
}
RuntimeResourceDefinition def = getResourceType(theReq);
returnsResource = false;
client.search(def.getImplementingClass(), params);
} else {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "Invalid method: " + method);
return;
}
} catch (BaseServerResponseException e) {
ourLog.error("Failed to invoke method", e);
returnsResource=false;
}
HttpRequestBase lastRequest = client.getLastRequest();
String requestBody = null;
String requestSyntaxHighlighterClass = null;
if (lastRequest instanceof HttpEntityEnclosingRequest) {
HttpEntityEnclosingRequest lastEERequest = (HttpEntityEnclosingRequest) lastRequest;
HttpEntity lastEE = lastEERequest.getEntity();
if (lastEE.isRepeatable()) {
StringWriter requestCapture = new StringWriter();
lastEE.writeTo(new WriterOutputStream(requestCapture, "UTF-8"));
requestBody = requestCapture.toString();
ContentType ct = ContentType.get(lastEE);
String mimeType = ct.getMimeType();
EncodingEnum ctEnum = EncodingEnum.forContentType(mimeType);
if (ctEnum == null) {
requestSyntaxHighlighterClass = "brush: plain";
} else {
switch (ctEnum) {
case JSON:
requestSyntaxHighlighterClass = "brush: jscript";
break;
case XML:
default:
requestSyntaxHighlighterClass = "brush: xml";
break;
}
}
}
}
requestUrl = lastRequest.getURI().toASCIIString();
action = client.getLastRequest().getMethod();
resultStatus = client.getLastResponse().getStatusLine().toString();
resultBody = client.getLastResponseBody();
HttpResponse lastResponse = client.getLastResponse();
ContentType ct = ContentType.get(lastResponse.getEntity());
String mimeType = ct != null ? ct.getMimeType() : null;
EncodingEnum ctEnum = EncodingEnum.forContentType(mimeType);
String narrativeString = "";
if (ctEnum == null) {
resultSyntaxHighlighterClass = "brush: plain";
} else {
switch (ctEnum) {
case JSON:
resultSyntaxHighlighterClass = "brush: jscript";
if (returnsResource) {
narrativeString = parseNarrative(ctEnum, resultBody);
}
break;
case XML:
default:
resultSyntaxHighlighterClass = "brush: xml";
if (returnsResource) {
narrativeString = parseNarrative(ctEnum, resultBody);
}
break;
}
}
WebContext ctx = new WebContext(theReq, theResp, theReq.getServletContext(), theReq.getLocale());
ctx.setVariable("base", myServerBase);
ctx.setVariable("requestUrl", requestUrl);
ctx.setVariable("action", action);
ctx.setVariable("resultStatus", resultStatus);
ctx.setVariable("requestBody", StringEscapeUtils.escapeHtml4(requestBody));
ctx.setVariable("requestSyntaxHighlighterClass", requestSyntaxHighlighterClass);
ctx.setVariable("resultBody", StringEscapeUtils.escapeHtml4(resultBody));
ctx.setVariable("resultSyntaxHighlighterClass", resultSyntaxHighlighterClass);
ctx.setVariable("requestHeaders", lastRequest.getAllHeaders());
ctx.setVariable("responseHeaders", lastResponse.getAllHeaders());
ctx.setVariable("narrative", narrativeString);
myTemplateEngine.process(PUBLIC_TESTER_RESULT_HTML, ctx, theResp.getWriter());
} catch (Exception e) {
ourLog.error("Failure during processing", e);
theResp.sendError(500, e.toString());
}
}
private String parseNarrative(EncodingEnum theCtEnum, String theResultBody) {
try {
IResource resource = theCtEnum.newParser(myCtx).parseResource(theResultBody);
String retVal = resource.getText().getDiv().getValueAsString();
return StringUtils.defaultString(retVal);
} catch (Exception e) {
ourLog.error("Failed to parse resource", e);
return "";
}
}
private interface ConformanceClient extends IBasicClient {
@Metadata
Conformance getConformance();
}
@Override
public void init(ServletConfig theConfig) throws ServletException {
myTemplateEngine = new TemplateEngine();
TemplateResolver resolver = new TemplateResolver();
resolver.setResourceResolver(new ProfileResourceResolver());
myTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect();
myTemplateEngine.setDialect(dialect);
myTemplateEngine.initialize();
}
private static final String PUBLIC_TESTER_RESULT_HTML = "/PublicTesterResult.html";
private final class ProfileResourceResolver implements IResourceResolver {
@Override
@ -285,10 +454,4 @@ public class PublicTesterServlet extends HttpServlet {
}
}
private void streamResponse(String theResourceName, String theContentType, HttpServletResponse theResp) throws IOException {
InputStream res = PublicTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/" + theResourceName);
theResp.setContentType(theContentType);
IOUtils.copy(res, theResp.getOutputStream());
}
}

View File

@ -1,7 +1,22 @@
A.selectedFunctionLink {
text-decoration: none;
background: #E0FFE0;
border: 1px #80C080 solid;
color: #005000;
}
BODY {
font-family: sans-serif;
}
SPAN.headerName {
color: #505080;
}
SPAN.headerValue {
color: #70A070;
}
DIV.bodyHeaderBlock {
background-color: #E0E0E0;
margin-top: 5px;
@ -9,10 +24,27 @@ DIV.bodyHeaderBlock {
padding: 5px;
}
DIV.textareaWrapper {
position:relative;
height:100%;
width:100%;
}
DIV.textareaWrapper TEXTAREA {
width:95%;
overflow: scroll;
}
TABLE.propertyTable {
margin-left: 4px;
width: 95%;
}
TD.propertyKeyCell {
background-color: #E0E0FF;
border-radius: 3px;
padding: 3px;
width: 100px;
}
TD.testerNameCell {

View File

@ -35,7 +35,7 @@ This file is a Thymeleaf template for the
<div class="bodyHeaderBlock">
Software Details
</div>
<table border="0">
<table border="0" class="propertyTable">
<tr>
<td class="propertyKeyCell">Software</td>
<td th:text="${conf.software.name}">HAPI Restful Server</td>
@ -45,36 +45,76 @@ This file is a Thymeleaf template for the
<td th:text="${conf.software.version}">1.1.1</td>
</tr>
</table>
<div class="bodyHeaderBlock">
Request Configuration
</div>
<table border="0" class="propertyTable">
<tr>
<td class="propertyKeyCell">Encoding</td>
<td>
<select id="configEncoding">
<option value="" selected="true">(default)</option>
<option value="xml">XML</option>
<option value="json">JSON</option>
</select>
</td>
</tr>
<tr>
<td class="propertyKeyCell">Pretty Printing</td>
<td>
<input type="checkbox" id="configPretty"/>
</td>
</tr>
</table>
</td>
<td width="1%"/>
<td width="70%" valign="top">
<th:block th:each="rest : ${conf.rest}">
<div class="bodyHeaderBlock">
RESTful Server
</div>
<table border="0" th:id="systemExpando" class="propertyTable">
<tr>
<td valign="top" class="propertyKeyCell">Supports operations:</td>
<td valign="top">
<a th:onclick="'displayConformance(this, \'systemExpando\'); return false;'" href="#">conformance</a>
<th:span th:each="operation : ${rest.operation}">
<th:block th:switch="${operation.code.value}">
<span th:case="*" th:text="${operation.code.value}"/>
</th:block>
</th:span>
</td>
</tr>
</table>
<th:block th:each="resource, resIterStat : ${rest.resource}" th:with="expandoId='resExpando'+${resIterStat.count}">
<div class="bodyHeaderBlock">
Resource: <th:block th:text="${resource.type.valueAsString}"/>
</div>
<table border="0" th:id="${expandoId}">
<table border="0" th:id="${expandoId}" class="propertyTable">
<tr>
<td valign="top" class="propertyKeyCell">Supports operations:</td>
<td valign="top">
<th:span th:each="operation : ${resource.operation}">
<th:block th:switch="${operation.code.value}">
<a th:case="'read'" th:href="'javascript:displayRead(\'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\');'">read</a>
<a th:case="'vread'" th:href="'javascript:displayVRead(\'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\');'">vread</a>
<a th:case="'search-type'" th:href="'javascript:displaySearchType(\'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\');'">search-type</a>
<span th:case="*" th:text="${operation.code.value}"/>
<a th:case="'read'" href="#" th:onclick="'displayRead(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">read</a>
<a th:case="'vread'" href="#" th:onclick="'displayVRead(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">vread</a>
<a th:case="'search-type'" href="#" th:onclick="'displaySearchType(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">search-type</a>
<a th:case="'create'" href="#" th:onclick="'displayCreate(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">create</a>
<a th:case="'update'" href="#" th:onclick="'displayUpdate(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">update</a>
<a th:case="'validate'" href="#" th:onclick="'displayValidate(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">validate</a>
<a th:case="'delete'" href="#" th:onclick="'displayDelete(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">delete</a>
<a th:case="'history-instance'" href="#" th:onclick="'displayHistoryInstance(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">history-instance</a>
<span th:case="*" href="#" th:text="${operation.code.value}"/>
</th:block>
</th:span>
</td>
</tr>
</table>
<div>
</div>
</th:block>
</th:block>

View File

@ -1,3 +1,24 @@
var uniqueIdSeed = 1;
function addConfigElementsToForm(theForm) {
var encoding = document.getElementById('configEncoding');
var pretty = document.getElementById('configPretty');
var newEncoding = document.createElement("input");
newEncoding.type='hidden';
newEncoding.name='configEncoding';
newEncoding.value = encoding.value;
theForm.appendChild(newEncoding);
var newPretty = document.createElement("input");
newPretty.type='hidden';
newPretty.name='configPretty';
if (pretty.checked) {
newPretty.value='on';
}
newPretty.checked = pretty.checked;
theForm.appendChild(newPretty);
}
/** Hide any currently displayed tester form */
function clearCurrentForm(postCompleteFunction) {
@ -20,14 +41,110 @@ function clearCurrentForm(postCompleteFunction) {
}
/** Create a tester form for the 'search' method */
function displaySearchType(expandoTr, resourceName) {
/** Create a tester form for the 'read' method */
function displayConformance(button, expandoTr) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Conformance</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'conformance', type: 'hidden' }),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'read' method */
function displayCreate(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Create</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'create', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<div class="textareaWrapper">').append(
$('<textarea />', { name: 'resource', rows: 10, style: 'white-space: nowrap;' })
),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'delete' method */
function displayDelete(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Delete</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'delete', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'read' method */
function displayUpdate(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Update</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'update', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
$('<textarea />', { name: 'resource', cols: 100, rows: 10, style: 'white-space: nowrap;' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'search' method */
function displaySearchType(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
// Add a search form for the default (no parameter) search, which should
// return all resources of the given type
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Search by Type</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST' }).append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'searchType', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<span>All Resources of Type</span><br />'),
@ -35,13 +152,15 @@ function displaySearchType(expandoTr, resourceName) {
)
)
)
);
);
// Loop through each supported search parameter and add a search form for it
conformance.rest.forEach(function(rest){
rest.resource.forEach(function(restResource){
if (restResource.type == resourceName) {
if (restResource.searchParam) {
restResource.searchParam.forEach(function(searchParam){
var formElement = $('<form/>', { action: 'PublicTesterResult.html', method: 'POST' });
var formElement = $('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" });
formElement.append(
$('<input />', { name: 'method', value: 'searchType', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' })
@ -87,14 +206,38 @@ function displaySearchType(expandoTr, resourceName) {
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'read' method */
function displayRead(expandoTr, resourceName) {
/** Create a tester form for the 'instance-history' method */
function displayHistoryInstance(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Read</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST' }).append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'history-instance', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'read' method */
function displayRead(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Read</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'read', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
@ -110,13 +253,37 @@ function displayRead(expandoTr, resourceName) {
}
/** Create a tester form for the 'read' method */
function displayVRead(expandoTr, resourceName) {
function displayValidate(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Validate</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'validate', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<textarea />', { name: 'resource', cols: 100, rows: 10, style: 'white-space: nowrap;' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'read' method */
function displayVRead(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Read</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST' }).append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'vread', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
@ -132,7 +299,13 @@ function displayVRead(expandoTr, resourceName) {
clearCurrentForm(postCompleteFunction);
}
var uniqueIdSeed = 1;
function highlightSelectedLink(button) {
$('.selectedFunctionLink').each(function() {
$(this).removeClass('selectedFunctionLink');
});
button.className = 'selectedFunctionLink';
}
/** Generate a unique ID */
function newUniqueId() {
return "uid" + uniqueIdSeed++;

View File

@ -28,9 +28,7 @@ This file is a Thymeleaf template for the
</tr>
</table>
<div class="bodyHeaderBlock">
Executed invocation:<br/>
<th:block th:text="${action}">GET</th:block>
<a href="http://foo.com/fhir" th:href="${requestUrl}"><th:block th:text="${requestUrl}">http://foo.com/fhir</th:block></a>
Executed invocation against FHIR RESTful Server
</div>
<table border="0" width="100%" cellpadding="0" cellspacing="0" style="margin-top: 4px;">
@ -52,23 +50,62 @@ This file is a Thymeleaf template for the
<td width="1%"/>
<td width="70%" valign="top">
<div class="bodyHeaderBlock">
Request
</div>
<table border="0" class="propertyTable">
<tr>
<td class="propertyKeyCell">Action</td>
<td th:text="${action}">GET</td>
</tr>
<tr>
<td class="propertyKeyCell">URL</td>
<td>
<a href="http://foo.com/fhir" th:href="${requestUrl}"><th:block th:text="${requestUrl}">http://foo.com/fhir</th:block></a>
</td>
</tr>
<tr > <!-- th:if="${not #lists.isEmpty(requestHeaders)}" -->
<td valign="top" class="propertyKeyCell">Headers</td>
<td valign="top">
<div th:each="header : ${requestHeaders}">
<span class="headerName" th:text="${header.name} + ': '"/>
<span class="headerValue" th:text="${header.value}"/>
</div>
</td>
</tr>
<tr th:if="${requestBody} != null">
<td valign="top" class="propertyKeyCell">Request Body</td>
<td valign="top"><pre th:text="${requestBody}" th:class="${requestSyntaxHighlighterClass}">{}</pre></td>
</tr>
</table>
<div class="bodyHeaderBlock">
Response
</div>
<table border="0">
<table border="0" class="propertyTable">
<tr>
<td valign="top" class="propertyKeyCell">Status</td>
<td valign="top" th:text="${resultStatus}">HTTP 200 OK</td>
</tr>
<tr>
<td valign="top" class="propertyKeyCell">Result</td>
<tr > <!-- th:if="${not #lists.isEmpty(responseHeaders)}" -->
<td valign="top" class="propertyKeyCell">Headers</td>
<td valign="top">
<div th:each="header : ${responseHeaders}">
<span class="headerName" th:text="${header.name} + ': '"/>
<span class="headerValue" th:text="${header.value}"/>
</div>
</td>
</tr>
<tr th:if="${!#strings.isEmpty(resultBody)}">
<td valign="top" class="propertyKeyCell">Result Body</td>
<td valign="top"><pre th:text="${resultBody}" th:class="${resultSyntaxHighlighterClass}">{}</pre></td>
</tr>
<tr th:if="${!#strings.isEmpty(narrative)}">
<td valign="top" class="propertyKeyCell">Result Narrative</td>
<td valign="top" th:utext="${narrative}"></td>
</tr>
</table>
<div>
</div>
</td>
</tr>

View File

@ -103,7 +103,7 @@
<td>
<a href="#instance_update">Type - Search</a>
<macro name="toc">
<param name="section" value="7" />
<param name="section" value="8" />
<param name="fromDepth" value="2" />
</macro>
</td>
@ -451,7 +451,7 @@
</p>
</subsection>
<subsection name="Search with a String Parameter">
<subsection name="Search Parameters: Introduction (String)">
<p>
To allow a search using given search parameters, add one or more parameters
@ -482,12 +482,18 @@
</p>
</subsection>
<subsection name="Search By Identifier">
<subsection name="Search Parameters: Token/Identifier">
<p>
Searches method parameters may be of any type that implements the
<a href="./apidocs/ca/uhn/fhir/model/api/IQueryParameterType.html">IQueryParameterType</a>
interface. For example, the search below can be used to search by
interface.
</p>
<p>
The "token" type is used for parameters which have two parts, such as
an idnetifier (which has a system URI, as well as the actual identifier)
or a code (which has a code system, as well as the actual code).
For example, the search below can be used to search by
identifier (e.g. search for an MRN).
</p>
@ -503,11 +509,122 @@
</p>
</subsection>
<subsection name="Multiple Parameters">
<subsection name="Search Parameters: Date (Simple)">
<p>
The FHIR specification provides a sytax for specifying
dates (and date/times as well, but for simplicity we will just say dates here)
as search criteria.
</p>
<p>
Dates may be optionally prefixed with a qualifier. For example, the
string
<code>&gt;=2011-01-02</code>
means any date on or after 2011-01-02.
</p>
<p>
To accept a qualified date parameter, use the
QualifiedDateParam
parameter type.
</p>
<macro name="snippet">
<param name="id" value="dates" />
<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/Observation?birthdate=&gt;=2011-01-02</code>
</p>
<p>
Invoking a client of thie type involves the following syntax:
</p>
<macro name="snippet">
<param name="id" value="dateClient" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</subsection>
<a name="DATE_RANGES" />
<subsection name="Search Parameters: Date (Ranges)">
<p>
A common scenario in searches is to allow searching for resources
with values (i.e timestamps) within a range of dates.
</p>
<p>
FHIR allows for multiple parameters with the same key, and interprets
these as being an "AND" set. So, for example, a range of
<br />
<code>DATE &gt;= 2011-01-01</code>
and
<code>DATE &lt; 2011-02-01</code>
<br />
can be interpreted as any date within January 2011.
</p>
<p>
The following snippet shows how to accept such a range, and combines it
with a specific identifier, which is a common scenario. (i.e. Give me a list
of observations for a
specific patient within a given date range)
</p>
<macro name="snippet">
<param name="id" value="dateRange" />
<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/Observation?subject.identifier=7000135&amp;date=&gt;=2011-01-01&amp;date=&lt;2011-02-01</code>
</p>
<p>
Invoking a client of this type involves the following syntax:
</p>
<macro name="snippet">
<param name="id" value="dateClient" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<h4>Unbounded Ranges</h4>
<p>
Note that when using a date range parameter, it is also possible for
the client to request an "unbounded" range. In other words, a range that
only a start date and no end date, or vice versa.
</p>
<p>
An example of this might be the following URL, which refers to any Observation
resources for the given MRN and having a date after 2011-01-01.
<br/>
<code>http://fhir.example.com/Observation?subject.identifier=7000135&amp;date=&gt;=2011-01-01</code><br/>
When such a request is made of a server (or to make such a request from a client),
the <code>getLowerBound()</code> or <code>getUpperBound()</code> property of the
<code>DateRangeParam</code> object will be set to <code>null</code>.
</p>
</subsection>
<subsection name="Combining Multiple Parameters">
<p>
Search methods may take multiple parameters, and these parameters
may be optional. To add a second required parameter, annotate the
may (or may not) be optional.
aaaa
To add a second required parameter, annotate the
parameter with
<a href="./apidocs/ca/uhn/fhir/rest/server/parameters/RequiredParam.html">@RequiredParam</a>
.
@ -614,96 +731,6 @@
</subsection>
<subsection name="Date Parameters - Simple">
<p>
The FHIR specification provides a sytax for specifying
dates (and date/times as well, but for simplicity we will just say dates here)
as search criteria.
</p>
<p>
Dates may be optionally prefixed with a qualifier. For example, the
string
<code>&gt;=2011-01-02</code>
means any date on or after 2011-01-02.
</p>
<p>
To accept a qualified date parameter, use the
QualifiedDateParam
parameter type.
</p>
<macro name="snippet">
<param name="id" value="dates" />
<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/Observation?birthdate=&gt;=2011-01-02</code>
</p>
<p>
Invoking a client of thie type involves the following syntax:
</p>
<macro name="snippet">
<param name="id" value="dateClient" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</subsection>
<a name="DATE_RANGES" />
<subsection name="Date Parameters - Ranges">
<p>
A common scenario in searches is to allow searching for resources
with values (i.e timestamps) within a range of dates.
</p>
<p>
FHIR allows for multiple parameters with the same key, and interprets
these as being an "AND" set. So, for example, a range of
<br />
<code>DATE &gt;= 2011-01-01</code>
and
<code>DATE &lt; 2011-02-01</code>
<br />
can be interpreted as any date within January 2011.
</p>
<p>
The following snippet shows how to accept such a range, and combines it
with a specific identifier, which is a common scenario. (i.e. Give me a list
of observations for a
specific patient within a given date range)
</p>
<macro name="snippet">
<param name="id" value="dateRange" />
<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/Observation?subject.identifier=7000135&amp;date=&gt;=2011-01-01&amp;date=&lt;2011-02-01</code>
</p>
<p>
Invoking a client of this type involves the following syntax:
</p>
<macro name="snippet">
<param name="id" value="dateClient" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</subsection>
<subsection name="Resource Includes (_include)">
<p>

View File

@ -5,14 +5,11 @@ import java.util.List;
import ca.uhn.fhir.model.api.BaseResource;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IExtension;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.dstu.composite.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
@ -44,8 +41,6 @@ public class ResourceWithExtensionsA extends BaseResource {
@Child(name = "identifier", type = IdentifierDt.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
private List<IdentifierDt> myIdentifier;
private IdDt myId;
public List<Bar1> getBar1() {
return myBar1;
}

View File

@ -60,7 +60,7 @@ public class XmlParserTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParserTest.class);
@Test
public void testEncodeBoundCode() throws IOException {
public void testEncodeBoundCode() {
Patient patient = new Patient();
patient.addAddress().setUse(AddressUseEnum.HOME);
@ -73,7 +73,7 @@ public class XmlParserTest {
}
@Test
public void testEncodeExtensionWithResourceContent() throws IOException {
public void testEncodeExtensionWithResourceContent() {
IParser parser = new FhirContext().newXmlParser();
Patient patient = new Patient();
@ -94,7 +94,7 @@ public class XmlParserTest {
}
@Test
public void testEncodeDeclaredExtensionWithResourceContent() throws IOException {
public void testEncodeDeclaredExtensionWithResourceContent() {
IParser parser = new FhirContext().newXmlParser();
MyPatientWithOneDeclaredExtension patient = new MyPatientWithOneDeclaredExtension();
@ -113,7 +113,7 @@ public class XmlParserTest {
}
@Test
public void testEncodeDeclaredExtensionWithAddressContent() throws IOException {
public void testEncodeDeclaredExtensionWithAddressContent() {
IParser parser = new FhirContext().newXmlParser();
MyPatientWithOneDeclaredAddressExtension patient = new MyPatientWithOneDeclaredAddressExtension();
@ -132,7 +132,7 @@ public class XmlParserTest {
}
@Test
public void testEncodeUndeclaredExtensionWithAddressContent() throws IOException {
public void testEncodeUndeclaredExtensionWithAddressContent() {
IParser parser = new FhirContext().newXmlParser();
Patient patient = new Patient();
@ -151,7 +151,7 @@ public class XmlParserTest {
}
@Test
public void testEncodeBundleResultCount() throws IOException {
public void testEncodeBundleResultCount() {
Bundle b = new Bundle();
b.getTotalResults().setValue(123);
@ -166,7 +166,7 @@ public class XmlParserTest {
@Test
public void testEncodeContainedResources() throws IOException {
public void testEncodeContainedResources() {
DiagnosticReport rpt = new DiagnosticReport();
Specimen spm = new Specimen();
@ -189,7 +189,7 @@ public class XmlParserTest {
}
@Test
public void testEncodeInvalidChildGoodException() throws IOException {
public void testEncodeInvalidChildGoodException() {
Observation obs = new Observation();
obs.setValue(new DecimalDt(112.22));
@ -203,7 +203,7 @@ public class XmlParserTest {
}
@Test
public void testEncodeResourceRef() throws DataFormatException, IOException {
public void testEncodeResourceRef() throws DataFormatException {
Patient patient = new Patient();
patient.setManagingOrganization(new ResourceReferenceDt());
@ -226,7 +226,7 @@ public class XmlParserTest {
@Test
public void testExtensions() throws DataFormatException, IOException {
public void testExtensions() throws DataFormatException {
MyPatient patient = new MyPatient();
patient.setPetName(new StringDt("Fido"));
@ -425,7 +425,7 @@ public class XmlParserTest {
@Test
public void testNarrativeGeneration() throws DataFormatException, IOException {
public void testNarrativeGeneration() throws DataFormatException {
Patient patient = new Patient();

View File

@ -79,34 +79,6 @@ public class ClientTest {
httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
}
private String getPatientFeedWithOneResult() {
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" +
"<title/>\n" +
"<id>d039f91a-cc3c-4013-988e-af4d8d0614bd</id>\n" +
"<os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">1</os:totalResults>\n" +
"<published>2014-03-11T16:35:07-04:00</published>\n" +
"<author>\n" +
"<name>ca.uhn.fhir.rest.server.DummyRestfulServer</name>\n" +
"</author>\n" +
"<entry>\n" +
"<content type=\"text/xml\">"
+ "<Patient xmlns=\"http://hl7.org/fhir\">"
+ "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>"
+ "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>"
+ "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>"
+ "<name><family value=\"Kramer\" /><given value=\"Doe\" /></name>"
+ "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>"
+ "<gender><coding><system value=\"http://hl7.org/fhir/v3/AdministrativeGender\" /><code value=\"M\" /></coding></gender>"
+ "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />"
+ "</Patient>"
+ "</content>\n"
+ " </entry>\n"
+ "</feed>";
//@formatter:on
return msg;
}
@Test
public void testCreate() throws Exception {
@ -209,8 +181,27 @@ public class ClientTest {
@Test
public void testHistoryResourceInstance() throws Exception {
InstantDt date1 = new InstantDt(new Date(20000L));
InstantDt date2 = new InstantDt(new Date(10000L));
InstantDt date3 = new InstantDt(new Date(30000L));
InstantDt date4 = new InstantDt(new Date(10000L));
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id>"
+ "<link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/>"
+ "<link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults>"
+ "<published>2014-04-13T18:24:50-04:00</published>"
+ "<author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author>"
+ "<entry><title>Patient 222</title><id>222</id>"
+ "<updated>"+date1.getValueAsString()+"</updated>"
+ "<published>"+date2.getValueAsString()+"</published>"
+ "<link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/>"
+ "<content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content>"
+ "</entry>"
+ "<entry><title>Patient 222</title><id>222</id>"
+ "<updated>"+date3.getValueAsString()+"</updated>"
+ "<published>"+date4.getValueAsString()+"</published>"
+ "<link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@ -260,55 +251,30 @@ public class ClientTest {
}
}
@Test
public void testHistoryWithParams() throws Exception {
//@formatter:off
final String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_ATOM_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02&_count=12", capt.getAllValues().get(0).getURI().toString());
String expectedDateString= new InstantDt(new InstantDt("2012-01-02T00:01:02").getValue()).getValueAsString(); // ensures the local timezone
expectedDateString=expectedDateString.replace(":", "%3A");
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02").getValue(), new IntegerDt(12).getValue());
assertEquals("http://foo/Patient/111/_history?_since="+expectedDateString+"&_count=12", capt.getAllValues().get(1).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), null, new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), null);
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02", capt.getAllValues().get(3).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt(), new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt());
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02", capt.getAllValues().get(3).getURI().toString());
}
@Test
public void testHistoryResourceType() throws Exception {
InstantDt date1 = new InstantDt(new Date(20000L));
InstantDt date2 = new InstantDt(new Date(10000L));
InstantDt date3 = new InstantDt(new Date(30000L));
InstantDt date4 = new InstantDt(new Date(10000L));
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id>"
+ "<link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/>"
+ "<link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults>"
+ "<published>2014-04-13T18:24:50-04:00</published>"
+ "<author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author>"
+ "<entry><title>Patient 222</title><id>222</id>"
+ "<updated>"+date1.getValueAsString()+"</updated>"
+ "<published>"+date2.getValueAsString()+"</published>"
+ "<link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/>"
+ "<content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content>"
+ "</entry>"
+ "<entry><title>Patient 222</title><id>222</id>"
+ "<updated>"+date3.getValueAsString()+"</updated>"
+ "<published>"+date4.getValueAsString()+"</published>"
+ "<link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@ -358,11 +324,30 @@ public class ClientTest {
}
}
@Test
public void testHistoryServer() throws Exception {
InstantDt date1 = new InstantDt(new Date(20000L));
InstantDt date2 = new InstantDt(new Date(10000L));
InstantDt date3 = new InstantDt(new Date(30000L));
InstantDt date4 = new InstantDt(new Date(10000L));
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id>"
+ "<link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/>"
+ "<link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults>"
+ "<published>2014-04-13T18:24:50-04:00</published>"
+ "<author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author>"
+ "<entry><title>Patient 222</title><id>222</id>"
+ "<updated>"+date1.getValueAsString()+"</updated>"
+ "<published>"+date2.getValueAsString()+"</published>"
+ "<link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/>"
+ "<content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content>"
+ "</entry>"
+ "<entry><title>Patient 222</title><id>222</id>"
+ "<updated>"+date3.getValueAsString()+"</updated>"
+ "<published>"+date4.getValueAsString()+"</published>"
+ "<link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@ -411,6 +396,49 @@ public class ClientTest {
assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString());
}
}
@Test
public void testHistoryWithParams() throws Exception {
//@formatter:off
final String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_ATOM_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02&_count=12", capt.getAllValues().get(0).getURI().toString());
String expectedDateString= new InstantDt(new InstantDt("2012-01-02T00:01:02").getValue()).getValueAsString(); // ensures the local timezone
expectedDateString=expectedDateString.replace(":", "%3A");
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02").getValue(), new IntegerDt(12).getValue());
assertEquals("http://foo/Patient/111/_history?_since="+expectedDateString+"&_count=12", capt.getAllValues().get(1).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), null, new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), null);
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02", capt.getAllValues().get(3).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt(), new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt());
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02", capt.getAllValues().get(3).getURI().toString());
}
@Test
public void testRead() throws Exception {
@ -488,62 +516,6 @@ public class ClientTest {
}
@Test
public void testSearchWithCustomType() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClientWithCustomType client = ctx.newRestfulClient(ITestClientWithCustomType.class, "http://foo");
CustomPatient response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue());
}
public interface ITestClientWithCustomType extends IBasicClient {
@Search()
public CustomPatient getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
}
@Test
public void testSearchWithCustomTypeList() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClientWithCustomTypeList client = ctx.newRestfulClient(ITestClientWithCustomTypeList.class, "http://foo");
List<CustomPatient> response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.get(0).getIdentifier().get(0).getValue().getValue());
}
public interface ITestClientWithCustomTypeList extends IBasicClient {
@Search()
public List<CustomPatient> getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
}
@ResourceDef(name="Patient")
public static class CustomPatient extends Patient
{
// nothing
}
@Test
public void testSearchByToken() throws Exception {
@ -563,6 +535,7 @@ public class ClientTest {
}
@Test
public void testSearchComposite() throws Exception {
@ -583,7 +556,7 @@ public class ClientTest {
assertEquals("http://foo/Patient?ids=foo%7Cbar%2Cbaz%7Cboz", capt.getValue().getURI().toString());
}
@Test
public void testSearchNamedQueryNoParams() throws Exception {
@ -619,7 +592,46 @@ public class ClientTest {
assertEquals("http://foo/Patient?_query=someQueryOneParam&param1=BB", capt.getValue().getURI().toString());
}
@Test
public void testSearchWithCustomType() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClientWithCustomType client = ctx.newRestfulClient(ITestClientWithCustomType.class, "http://foo");
CustomPatient response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue());
}
@Test
public void testSearchWithCustomTypeList() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClientWithCustomTypeList client = ctx.newRestfulClient(ITestClientWithCustomTypeList.class, "http://foo");
List<CustomPatient> response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.get(0).getIdentifier().get(0).getValue().getValue());
}
@Test
public void testSearchWithIncludes() throws Exception {
@ -638,29 +650,6 @@ public class ClientTest {
}
@Test
public void testSearchWithStringIncludes() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClientWithStringIncludes client = ctx.newRestfulClient(ITestClientWithStringIncludes.class, "http://foo");
client.getPatientWithIncludes(new StringDt("aaa"), "inc1");
assertEquals("http://foo/Patient?withIncludes=aaa&_include=inc1", capt.getValue().getURI().toString());
}
public interface ITestClientWithStringIncludes extends IBasicClient {
@Search()
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam String theInclude);
}
@Test
public void testSearchWithOptionalParam() throws Exception {
@ -693,6 +682,24 @@ public class ClientTest {
}
@Test
public void testSearchWithStringIncludes() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClientWithStringIncludes client = ctx.newRestfulClient(ITestClientWithStringIncludes.class, "http://foo");
client.getPatientWithIncludes(new StringDt("aaa"), "inc1");
assertEquals("http://foo/Patient?withIncludes=aaa&_include=inc1", capt.getValue().getURI().toString());
}
@Test
public void testUpdate() throws Exception {
@ -778,6 +785,31 @@ public class ClientTest {
assertEquals("200", response.getVersionId().getValue());
}
@Test
public void testValidate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
MethodOutcome response = client.validatePatient(patient);
assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue();
assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/_validate"));
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
assertEquals("100", response.getId().getValue());
assertEquals("200", response.getVersionId().getValue());
}
@Test
public void testVRead() throws Exception {
@ -808,7 +840,56 @@ public class ClientTest {
}
private String getPatientFeedWithOneResult() {
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" +
"<title/>\n" +
"<id>d039f91a-cc3c-4013-988e-af4d8d0614bd</id>\n" +
"<os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">1</os:totalResults>\n" +
"<published>2014-03-11T16:35:07-04:00</published>\n" +
"<author>\n" +
"<name>ca.uhn.fhir.rest.server.DummyRestfulServer</name>\n" +
"</author>\n" +
"<entry>\n" +
"<content type=\"text/xml\">"
+ "<Patient xmlns=\"http://hl7.org/fhir\">"
+ "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>"
+ "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>"
+ "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>"
+ "<name><family value=\"Kramer\" /><given value=\"Doe\" /></name>"
+ "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>"
+ "<gender><coding><system value=\"http://hl7.org/fhir/v3/AdministrativeGender\" /><code value=\"M\" /></coding></gender>"
+ "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />"
+ "</Patient>"
+ "</content>\n"
+ " </entry>\n"
+ "</feed>";
//@formatter:on
return msg;
}
private Header[] toHeaderArray(String theName, String theValue) {
return new Header[] { new BasicHeader(theName, theValue) };
}
@ResourceDef(name="Patient")
public static class CustomPatient extends Patient
{
// nothing
}
public interface ITestClientWithCustomType extends IBasicClient {
@Search()
public CustomPatient getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
}
public interface ITestClientWithCustomTypeList extends IBasicClient {
@Search()
public List<CustomPatient> getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
}
public interface ITestClientWithStringIncludes extends IBasicClient {
@Search()
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam String theInclude);
}
}

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IBasicClient;
@ -94,4 +95,7 @@ public interface ITestClient extends IBasicClient {
@Read(type=Patient.class)
Patient getPatientByVersionId(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId);
@Validate(type=Patient.class)
MethodOutcome validatePatient(@ResourceParam Patient thePatient);
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.StringReader;
@ -171,11 +172,6 @@ public class ResfulServerMethodTest {
@Test
public void testDelete() throws Exception {
// HttpPost httpPost = new HttpPost("http://localhost:" + ourPort +
// "/Patient/1");
// httpPost.setEntity(new StringEntity("test",
// ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpDelete httpGet = new HttpDelete("http://localhost:" + ourPort + "/Patient/1234");
HttpResponse status = ourClient.execute(httpGet);
@ -189,6 +185,22 @@ public class ResfulServerMethodTest {
}
@Test
public void testWithAdditionalParams() throws Exception {
HttpDelete httpGet = new HttpDelete("http://localhost:" + ourPort + "/Patient/1234?_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
OperationOutcome patient = ourCtx.newXmlParser().parseResource(OperationOutcome.class, responseContent);
assertEquals("1234", patient.getIssueFirstRep().getDetails().getValue());
}
@Test
public void testDeleteNoResponse() throws Exception {
@ -431,6 +443,28 @@ public class ResfulServerMethodTest {
}
@Test
public void testHistoryFailsIfResourcesAreIncorrectlyPopulated() throws Exception {
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/999/_history");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(500, status.getStatusLine().getStatusCode());
}
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/998/_history");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(500, status.getStatusLine().getStatusCode());
}
}
@Test
public void testHistoryResourceType() throws Exception {
@ -531,8 +565,7 @@ public class ResfulServerMethodTest {
assertEquals(2, bundle.getEntries().size());
}
@Test
public void testReadOnTypeThatDoesntSupportRead() throws Exception {
@ -865,19 +898,20 @@ public class ResfulServerMethodTest {
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
OperationOutcome oo =new FhirContext().newXmlParser().parseResource(OperationOutcome.class, responseContent);
OperationOutcome oo = new FhirContext().newXmlParser().parseResource(OperationOutcome.class, responseContent);
assertEquals("OODETAILS", oo.getIssueFirstRep().getDetails().getValue());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("Location").getValue());
}
public void testUpdateWrongResourceType() throws Exception {
// TODO: this method sends in the wrong resource type vs. the URL so it should
// give a useful error message (and then make this unit test actually run)
// TODO: this method sends in the wrong resource type vs. the URL so it
// should
// give a useful error message (and then make this unit test actually
// run)
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
@ -890,7 +924,7 @@ public class ResfulServerMethodTest {
assertEquals("http://localhost:" + ourPort + "/DiagnosticReport/001/_history/002", status.getFirstHeader("Location").getValue());
}
@Test
public void testUpdateNoResponse() throws Exception {
@ -906,8 +940,7 @@ public class ResfulServerMethodTest {
assertEquals("http://localhost:" + ourPort + "/DiagnosticReport/001/_history/002", status.getFirstHeader("Location").getValue());
}
@Test
public void testUpdateWithVersion() throws Exception {
@ -920,8 +953,9 @@ public class ResfulServerMethodTest {
HttpResponse status = ourClient.execute(httpPut);
// String responseContent = IOUtils.toString(status.getEntity().getContent());
// ourLog.info("Response was:\n{}", responseContent);
// String responseContent =
// IOUtils.toString(status.getEntity().getContent());
// ourLog.info("Response was:\n{}", responseContent);
assertEquals(204, status.getStatusLine().getStatusCode());
assertNull(status.getEntity());
@ -946,6 +980,22 @@ public class ResfulServerMethodTest {
assertEquals(400, results.getStatusLine().getStatusCode());
}
@Test
public void testValidateWithPrettyPrintResponse() throws Exception {
Patient patient = new Patient();
patient.addName().addFamily("FOO");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/_validate?_pretty=true");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertThat(responseContent, containsString("\n "));
}
@Test
public void testValidate() throws Exception {
@ -959,6 +1009,7 @@ public class ResfulServerMethodTest {
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertThat(responseContent, not(containsString("\n ")));
assertEquals(200, status.getStatusLine().getStatusCode());
OperationOutcome oo = new FhirContext().newXmlParser().parseResource(OperationOutcome.class, responseContent);
@ -991,12 +1042,12 @@ public class ResfulServerMethodTest {
status = ourClient.execute(httpPost);
// responseContent = IOUtils.toString(status.getEntity().getContent());
// ourLog.info("Response was:\n{}", responseContent);
// responseContent = IOUtils.toString(status.getEntity().getContent());
// ourLog.info("Response was:\n{}", responseContent);
assertEquals(204, status.getStatusLine().getStatusCode());
assertNull(status.getEntity());
// assertEquals("", responseContent);
// assertEquals("", responseContent);
}
@ -1084,24 +1135,23 @@ public class ResfulServerMethodTest {
/*
* *********************
* NO NEW METHODS
* *********************
* NO NEW METHODS *********************
*/
@Override
public Class<? extends IResource> getResourceType() {
return AdverseReaction.class;
}
@Create()
public MethodOutcome create(@ResourceParam AdverseReaction thePatient) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
IdDt version = new IdDt(thePatient.getIdentifier().get(1).getValue().getValue());
return new MethodOutcome(id, version);
}
}
/**
* Created by dsotnikov on 2/25/2014.
*/
@ -1132,19 +1182,28 @@ public class ResfulServerMethodTest {
public List<Patient> getHistoryResourceInstance(@IdParam IdDt theId) {
ArrayList<Patient> retVal = new ArrayList<Patient>();
IdDt id = theId;
if (id.getValue().equals("999")) {
id = null; // to test the error when no ID is present
}
Patient older = createPatient1();
older.setId(theId);
older.setId(id);
older.getNameFirstRep().getFamilyFirstRep().setValue("OlderFamily");
older.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "1");
older.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new Date(10000L));
older.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt(new Date(20000L)));
older.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "1");
if (id != null && !id.getValue().equals("998")) {
older.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "1");
}
retVal.add(older);
Patient newer = createPatient1();
newer.setId(theId);
newer.getNameFirstRep().getFamilyFirstRep().setValue("NewerFamily");
newer.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "2");
if (id != null && !id.getValue().equals("998")) {
newer.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "2");
}
newer.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new Date(10000L));
newer.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt(new Date(30000L)));
retVal.add(newer);

View File

@ -1,14 +1,11 @@
package ca.uhn.fhir.rest.server;
import java.lang.annotation.Documented;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
@ -20,7 +17,7 @@ import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.composite.QuantityDt;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
@ -28,19 +25,26 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.tester.PublicTesterServlet;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
public class TesterTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TesterTest.class);
private int myPort;
private Server myServer;
private FhirContext myCtx;
@ -51,22 +55,24 @@ public class TesterTest {
myPort = RandomServerPortProvider.findFreePort();
myPort = 8888;
myServer = new Server(myPort);
myCtx = new FhirContext(Patient.class);
myRestfulServer = new RestfulServer();
myCtx = new FhirContext();
myCtx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
myRestfulServer = new RestfulServer(myCtx);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
PublicTesterServlet testerServlet = new PublicTesterServlet();
testerServlet.setServerBase("http://localhost:" + myPort + "/fhir/context");
// testerServlet.setServerBase("http://fhir.healthintersections.com.au/open");
// testerServlet.setServerBase("http://fhir.healthintersections.com.au/open");
ServletHolder handler = new ServletHolder();
handler.setServlet(testerServlet);
proxyHandler.addServlet(handler, "/fhir/tester/*");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(myRestfulServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
myServer.setHandler(proxyHandler);
}
@ -78,13 +84,13 @@ public class TesterTest {
@Test
public void testTester() throws Exception {
if (true) return;
if (true) return;
myRestfulServer.setProviders(new SearchProvider(), new GlobalHistoryProvider());
myServer.start();
Thread.sleep(9999999L);
}
/**
@ -92,11 +98,27 @@ public class TesterTest {
*/
public static class SearchProvider {
public Map<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
private int myNextId = 1;
private HashMap<String, Patient> myIdToPatient;
private static Patient createPatient() {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
return patient;
}
public SearchProvider() {
myIdToPatient = new HashMap<String, Patient>();
{
Patient patient = createPatient();
idToPatient.put("1", patient);
myIdToPatient.put("" + myNextId++, patient);
}
{
Patient patient = new Patient();
@ -108,17 +130,42 @@ public class TesterTest {
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientTwo");
patient.getGender().setText("F");
idToPatient.put("2", patient);
myIdToPatient.put("" + myNextId++, patient);
}
return idToPatient;
}
@Create
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
IdDt id = new IdDt(myNextId++);
myIdToPatient.put(id.getValueAsString(), thePatient);
return new MethodOutcome(id);
}
@SuppressWarnings("unused")
@Validate
public MethodOutcome validatePatient(@ResourceParam Patient thePatient) {
MethodOutcome outcome = new MethodOutcome();
outcome.setOperationOutcome(new OperationOutcome());
outcome.getOperationOutcome().addIssue().setDetails("This is a detected issue");
return outcome;
}
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) {
myIdToPatient.put(theId.getValueAsString(), thePatient);
return new MethodOutcome(theId);
}
@Delete(type=Patient.class)
public MethodOutcome deletePatient(@IdParam IdDt theId) {
myIdToPatient.remove(theId.getValue());
return new MethodOutcome();
}
@Search(type = Patient.class)
public Patient findPatient(
@Description(shortDefinition="The patient's identifier (MRN or other card number). Example system 'urn:hapitest:mrns', example MRN '00002'")
@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier
) {
for (Patient next : getIdToPatient().values()) {
public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number). Example system 'urn:hapitest:mrns', example MRN '00002'") @RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
for (Patient next : myIdToPatient.values()) {
for (IdentifierDt nextId : next.getIdentifier()) {
if (nextId.matchesSystemAndValue(theIdentifier)) {
return next;
@ -137,7 +184,30 @@ public class TesterTest {
*/
@Read(type = Patient.class)
public Patient getPatientById(@IdParam IdDt theId) {
return getIdToPatient().get(theId.getValue());
return myIdToPatient.get(theId.getValue());
}
/**
* Retrieve the resource by its identifier
*
* @param theId
* The resource identity
* @return The resource
*/
@History(type = Patient.class)
public List<Patient> getPatientHistory(@IdParam IdDt theId) {
Patient patient = myIdToPatient.get(theId.getValue());
if (patient==null) {
throw new ResourceNotFoundException(Patient.class, theId);
}
ArrayList<Patient> retVal = new ArrayList<Patient>();
for (int i = 0; i < 5;i++) {
Patient pat = createPatient();
pat.setId(theId);
pat.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, ""+i);
retVal.add(pat);
}
return retVal;
}
}
@ -153,14 +223,14 @@ public class TesterTest {
myLastCount = theCount;
ArrayList<IResource> retVal = new ArrayList<IResource>();
IResource p = createPatient();
IResource p = SearchProvider.createPatient();
p.setId(new IdDt("1"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("A"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2012-01-01T01:00:01"));
retVal.add(p);
p = createPatient();
p = SearchProvider.createPatient();
p.setId(new IdDt("1"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("B"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
@ -179,19 +249,7 @@ public class TesterTest {
}
private static Patient createPatient() {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
return patient;
}
private static Organization createOrganization() {
Organization retVal = new Organization();
retVal.addIdentifier();