Add the ability to configure the default pretty-print and encoding for a

server
This commit is contained in:
James Agnew 2015-03-11 14:16:32 -04:00
parent a2e562de64
commit 4ac0ef4374
8 changed files with 249 additions and 27 deletions

View File

@ -239,7 +239,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
servletResponse.setContentType(encoding.getResourceContentType());
Writer writer = servletResponse.getWriter();
IParser parser = encoding.newParser(getContext());
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theRequest));
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theServer, theRequest));
try {
parser.encodeResourceToWriter(response.getOperationOutcome(), writer);
} finally {
@ -311,7 +311,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
if (theE.getOperationOutcome() != null) {
theResponse.setContentType(theEncodingNotNull.getResourceContentType());
IParser parser = theEncodingNotNull.newParser(theServer.getFhirContext());
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theRequest));
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theServer, theRequest));
Writer writer = theResponse.getWriter();
try {
parser.encodeResourceToWriter(theE.getOperationOutcome(), writer);

View File

@ -220,7 +220,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
public void invokeServer(RestfulServer theServer, Request theRequest) throws BaseServerResponseException, IOException {
// Pretty print
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequest);
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
// Narrative mode
NarrativeModeEnum narrativeMode = RestfulServerUtils.determineNarrativeMode(theRequest);

View File

@ -178,7 +178,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
theServer.addHeadersToResponse(response);
IParser parser = responseEncoding.newParser(getContext());
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theRequest));
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theServer, theRequest));
PrintWriter writer = response.getWriter();
try {
parser.encodeTagListToWriter(resp, writer);

View File

@ -73,11 +73,12 @@ public class RestfulServer extends HttpServlet {
* Default setting for {@link #setETagSupport(ETagSupportEnum) ETag Support}: {@link ETagSupportEnum#ENABLED}
*/
public static final ETagSupportEnum DEFAULT_ETAG_SUPPORT = ETagSupportEnum.ENABLED;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L;
private AddProfileTagEnum myAddProfileTag;
private BundleInclusionRule myBundleInclusionRule = BundleInclusionRule.BASED_ON_INCLUDES;
private boolean myDefaultPrettyPrint = false;
private EncodingEnum myDefaultResponseEncoding = EncodingEnum.XML;
private ETagSupportEnum myETagSupport = DEFAULT_ETAG_SUPPORT;
private FhirContext myFhirContext;
@ -91,14 +92,11 @@ public class RestfulServer extends HttpServlet {
private ResourceBinding myServerBinding = new ResourceBinding();
private BaseMethodBinding<?> myServerConformanceMethod;
private Object myServerConformanceProvider;
private String myServerName = "HAPI FHIR Server";
private String myServerName = "HAPI FHIR Server";
/** This is configurable but by default we just use HAPI version */
private String myServerVersion = VersionUtil.getVersion();
private boolean myStarted;
private boolean myUseBrowserFriendlyContentTypes;
private boolean myUseBrowserFriendlyContentTypes;
/**
* Constructor
@ -107,7 +105,7 @@ public class RestfulServer extends HttpServlet {
this(new FhirContext());
}
public RestfulServer(FhirContext theCtx) {
public RestfulServer(FhirContext theCtx) {
myFhirContext = theCtx;
}
@ -452,7 +450,7 @@ public class RestfulServer extends HttpServlet {
int start = Math.min(offsetI, resultList.size() - 1);
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest());
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequest);
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(this, theRequest);
boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest());
NarrativeModeEnum narrativeMode = RestfulServerUtils.determineNarrativeMode(theRequest);
boolean respondGzip = theRequest.isRespondGzip();
@ -877,6 +875,19 @@ public class RestfulServer extends HttpServlet {
}
}
/**
* Should the server "pretty print" responses by default (requesting clients can always override this
* default by supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
* </p>
* @return Returns the default pretty print setting
*/
public boolean isDefaultPrettyPrint() {
return myDefaultPrettyPrint;
}
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes;
}
@ -913,6 +924,19 @@ public class RestfulServer extends HttpServlet {
myBundleInclusionRule = theBundleInclusionRule;
}
/**
* Should the server "pretty print" responses by default (requesting clients can always override this
* default by supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
* </p>
* @param theDefaultPrettyPrint The default pretty print setting
*/
public void setDefaultPrettyPrint(boolean theDefaultPrettyPrint) {
myDefaultPrettyPrint = theDefaultPrettyPrint;
}
/**
* Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with
* the <code>_format</code> URL parameter, or with an <code>Accept</code> header in the request. The default is

View File

@ -154,7 +154,7 @@ public class RestfulServerUtils {
}
}
public static boolean prettyPrintResponse(Request theRequest) {
public static boolean prettyPrintResponse(RestfulServer theServer, Request theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] pretty = requestParams.remove(Constants.PARAM_PRETTY);
boolean prettyPrint;
@ -165,7 +165,7 @@ public class RestfulServerUtils {
prettyPrint = false;
}
} else {
prettyPrint = false;
prettyPrint = theServer.isDefaultPrettyPrint();
Enumeration<String> acceptValues = theRequest.getServletRequest().getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {

View File

@ -5,12 +5,18 @@ import java.util.List;
import javax.servlet.ServletException;
import ca.uhn.fhir.context.FhirVersionEnum;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu1;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
@ -35,8 +41,8 @@ public class JpaServerDemo extends RestfulServer {
*
* If you want to use DSTU1 instead, change the following line, and change the 2 occurrences of dstu2 in web.xml to dstu1
*/
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2;
setFhirContext(new FhirContext(fhirVersion));
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2;
setFhirContext(new FhirContext(fhirVersion));
// Get the spring context from the web container (it's declared in web.xml)
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
@ -46,22 +52,39 @@ public class JpaServerDemo extends RestfulServer {
* file which is automatically generated as a part of hapi-fhir-jpaserver-base and
* contains bean definitions for a resource provider for each resource type
*/
String resourceProviderBeanName = "myResourceProvidersDstu" + (fhirVersion == FhirVersionEnum.DSTU1 ? "1" : "2");
List<IResourceProvider> beans = myAppCtx.getBean(resourceProviderBeanName, List.class);
String resourceProviderBeanName = "myResourceProvidersDstu" + (fhirVersion == FhirVersionEnum.DSTU1 ? "1" : "2");
List<IResourceProvider> beans = myAppCtx.getBean(resourceProviderBeanName, List.class);
setResourceProviders(beans);
/*
* The system provider implements non-resource-type methods, such as
* transaction, and global history.
*/
Object systemProvider;
if (fhirVersion == FhirVersionEnum.DSTU1) {
systemProvider = myAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class);
} else {
systemProvider = myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
}
Object systemProvider;
if (fhirVersion == FhirVersionEnum.DSTU1) {
systemProvider = myAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class);
} else {
systemProvider = myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
}
setPlainProviders(systemProvider);
/*
* The conformance provider exports the supported resources, search parameters, etc for
* this server. The JPA version adds resource counts to the exported statement, so it
* is a nice addition.
*/
if (fhirVersion == FhirVersionEnum.DSTU1) {
IFhirSystemDao<List<IResource>> systemDao = myAppCtx.getBean("mySystemDaoDstu1", IFhirSystemDao.class);
JpaConformanceProviderDstu1 confProvider = new JpaConformanceProviderDstu1(this, systemDao);
confProvider.setImplementationDescription("Example Server");
setServerConformanceProvider(confProvider);
} else {
IFhirSystemDao<Bundle> systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao);
confProvider.setImplementationDescription("Example Server");
setServerConformanceProvider(confProvider);
}
/*
* Enable ETag Support (this is already the default)
*/

View File

@ -0,0 +1,173 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.*;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.common.net.UrlEscapers;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.BaseResource;
import ca.uhn.fhir.model.dstu.resource.Binary;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.util.PortUtil;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class DefaultEncodingTest {
private static CloseableHttpClient ourClient;
private static int ourPort;
private static Server ourServer;
private static FhirContext ourCtx;
private static RestfulServer ourRestfulServer;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultEncodingTest.class);
@Test
public void testReadWithDefaultJsonPretty() throws Exception {
ourRestfulServer.setDefaultPrettyPrint(true);
ourRestfulServer.setDefaultResponseEncoding(EncodingEnum.JSON);
HttpGet httpGet;
HttpResponse status;
String responseContent;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
status = ourClient.execute(httpGet);
responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertThat(responseContent, containsString(" \"identifier\":"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_pretty=false");
status = ourClient.execute(httpGet);
responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertThat(responseContent, not(containsString(" \"identifier\":")));
assertThat(responseContent, containsString("\"identifier\":"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=xml");
status = ourClient.execute(httpGet);
responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertThat(responseContent, containsString(" <identifier"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=xml&_pretty=false");
status = ourClient.execute(httpGet);
responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertThat(responseContent, not(containsString(" <identifier")));
assertThat(responseContent, containsString("<identifier"));
}
@Test
public void testReadWithDefaultXmlUgly() throws Exception {
ourRestfulServer.setDefaultPrettyPrint(false);
ourRestfulServer.setDefaultResponseEncoding(EncodingEnum.XML);
HttpGet httpGet;
HttpResponse status;
String responseContent;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
status = ourClient.execute(httpGet);
responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertThat(responseContent, not(containsString(" <identifier")));
assertThat(responseContent, containsString("<identifier"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=xml");
status = ourClient.execute(httpGet);
responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertThat(responseContent, containsString("<identifier"));
assertThat(responseContent, not(containsString(" <identifier")));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=json");
status = ourClient.execute(httpGet);
responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertThat(responseContent, containsString("\"identifier\":"));
assertThat(responseContent, not(containsString(" \"identifier\":")));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=json&_pretty=true");
status = ourClient.execute(httpGet);
responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertThat(responseContent, containsString(" \"identifier\":"));
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler();
ourRestfulServer = new RestfulServer();
ourCtx = ourRestfulServer.getFhirContext();
ourRestfulServer.setResourceProviders(new PatientProvider());
ServletHolder servletHolder = new ServletHolder(ourRestfulServer);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class PatientProvider implements IResourceProvider {
@Read(version = true)
public Patient read(@IdParam IdDt theId) {
Patient patient = new Patient();
patient.addIdentifier(theId.getIdPart(), theId.getVersionIdPart());
patient.setId("Patient/1/_history/1");
return patient;
}
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
}
}

View File

@ -123,9 +123,11 @@
in accordance with the recommended behaviour in the FHIR specification.
</action>
<action type="add">
Add a new property to RestfulServer called "DefaultResponseEncoding", which allows
Add new properties to RestfulServer: "DefaultResponseEncoding", which allows
users to configure a default encoding (XML/JSON) to use if none is specified in the
client request. Currently defaults to XML.
client request. Currently defaults to XML. Also "DefaultPrettyPrint", which specifies
whether to pretty print responses by default. Both properties can be overridden
on individual requets using the appropriate Accept header or request URL parameters.
</action>
<action type="add">
Add support for quantity search params in FHIR tester UI