Add request ID

This commit is contained in:
James Agnew 2019-07-02 21:32:28 -04:00
parent 1faf7785c4
commit 56aaef641c
16 changed files with 157 additions and 53 deletions

View File

@ -50,7 +50,7 @@ public class ConsentInterceptors {
* Modify resources that are being shown to the user * Modify resources that are being shown to the user
*/ */
@Override @Override
public ConsentOutcome seeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
// Don't return the subject for Observation resources // Don't return the subject for Observation resources
if (theResource instanceof Observation) { if (theResource instanceof Observation) {
Observation obs = (Observation)theResource; Observation obs = (Observation)theResource;

View File

@ -25,6 +25,7 @@ import java.util.*;
public class Constants { public class Constants {
public static final String HEADER_REQUEST_ID = "X-Request-ID";
public static final String CACHE_CONTROL_MAX_RESULTS = "max-results"; public static final String CACHE_CONTROL_MAX_RESULTS = "max-results";
public static final String CACHE_CONTROL_NO_CACHE = "no-cache"; public static final String CACHE_CONTROL_NO_CACHE = "no-cache";
public static final String CACHE_CONTROL_NO_STORE = "no-store"; public static final String CACHE_CONTROL_NO_STORE = "no-store";

View File

@ -469,7 +469,7 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
} }
return ConsentOutcome.PROCEED; return ConsentOutcome.PROCEED;
}); });
when(svc.seeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(svc.willSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
consentService.setTarget(svc); consentService.setTarget(svc);
String query = "{ name { family, given }, managingOrganization { reference, resource {name} } }"; String query = "{ name { family, given }, managingOrganization { reference, resource {name} } }";
@ -502,7 +502,7 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
IConsentService svc = mock(IConsentService.class); IConsentService svc = mock(IConsentService.class);
when(svc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(svc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(svc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(svc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(svc.seeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t -> { when(svc.willSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t -> {
IBaseResource resource = t.getArgument(1, IBaseResource.class); IBaseResource resource = t.getArgument(1, IBaseResource.class);
if (resource instanceof Organization) { if (resource instanceof Organization) {
Organization org = new Organization(); Organization org = new Organization();
@ -598,7 +598,7 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
} }
@Override @Override
public ConsentOutcome seeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
mySeeCount++; mySeeCount++;
String resourceId = theResource.getIdElement().toUnqualifiedVersionless().getValue(); String resourceId = theResource.getIdElement().toUnqualifiedVersionless().getValue();
ourLog.info("** SEE: {}", resourceId); ourLog.info("** SEE: {}", resourceId);
@ -640,7 +640,7 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
} }
@Override @Override
public ConsentOutcome seeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
return ConsentOutcome.PROCEED; return ConsentOutcome.PROCEED;
} }
@ -674,7 +674,7 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
} }
@Override @Override
public ConsentOutcome seeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
return ConsentOutcome.PROCEED; return ConsentOutcome.PROCEED;
} }
@ -710,7 +710,7 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
} }
@Override @Override
public ConsentOutcome seeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
return ConsentOutcome.PROCEED; return ConsentOutcome.PROCEED;
} }
@ -740,7 +740,7 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
} }
@Override @Override
public ConsentOutcome seeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
return ConsentOutcome.PROCEED; return ConsentOutcome.PROCEED;
} }

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.rest.api.server;
*/ */
import java.io.*; import java.io.*;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
@ -48,6 +50,8 @@ public interface IRestfulResponse {
void setOperationResourceLastUpdated(IPrimitiveType<Date> theOperationResourceLastUpdated); void setOperationResourceLastUpdated(IPrimitiveType<Date> theOperationResourceLastUpdated);
Map<String, List<String>> getHeaders();
void setOperationResourceId(IIdType theOperationResourceId); void setOperationResourceId(IIdType theOperationResourceId);
} }

View File

@ -72,6 +72,7 @@ public abstract class RequestDetails {
private Map<String, List<String>> myUnqualifiedToQualifiedNames; private Map<String, List<String>> myUnqualifiedToQualifiedNames;
private Map<Object, Object> myUserData; private Map<Object, Object> myUserData;
private IBaseResource myResource; private IBaseResource myResource;
private String myRequestId;
/** /**
* Constructor * Constructor
@ -80,6 +81,14 @@ public abstract class RequestDetails {
myInterceptorBroadcaster = theInterceptorBroadcaster; myInterceptorBroadcaster = theInterceptorBroadcaster;
} }
public String getRequestId() {
return myRequestId;
}
public void setRequestId(String theRequestId) {
myRequestId = theRequestId;
}
public StopWatch getRequestStopwatch() { public StopWatch getRequestStopwatch() {
return myRequestStopwatch; return myRequestStopwatch;
} }

View File

@ -49,6 +49,7 @@ public abstract class RestfulResponse<T extends RequestDetails> implements IRest
* Get the http headers * Get the http headers
* @return the headers * @return the headers
*/ */
@Override
public Map<String, List<String>> getHeaders() { public Map<String, List<String>> getHeaders() {
return theHeaders; return theHeaders;
} }

View File

@ -77,8 +77,7 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class RestfulServer extends HttpServlet implements IRestfulServer<ServletRequestDetails> { public class RestfulServer extends HttpServlet implements IRestfulServer<ServletRequestDetails> {
@ -105,6 +104,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor(); private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor();
private static final Logger ourLog = LoggerFactory.getLogger(RestfulServer.class); private static final Logger ourLog = LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Random RANDOM = new Random();
private final List<Object> myPlainProviders = new ArrayList<>(); private final List<Object> myPlainProviders = new ArrayList<>();
private final List<IResourceProvider> myResourceProviders = new ArrayList<>(); private final List<IResourceProvider> myResourceProviders = new ArrayList<>();
private IInterceptorService myInterceptorService; private IInterceptorService myInterceptorService;
@ -171,6 +171,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
if (isNotBlank(poweredByHeader)) { if (isNotBlank(poweredByHeader)) {
theHttpResponse.addHeader(Constants.POWERED_BY_HEADER, poweredByHeader); theHttpResponse.addHeader(Constants.POWERED_BY_HEADER, poweredByHeader);
} }
} }
private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) { private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) {
@ -561,6 +563,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Returns a list of all registered server interceptors * Returns a list of all registered server interceptors
*
* @deprecated As of HAPI FHIR 3.8.0, use {@link #getInterceptorService()} to access the interceptor service. You can register and unregister interceptors using this service. * @deprecated As of HAPI FHIR 3.8.0, use {@link #getInterceptorService()} to access the interceptor service. You can register and unregister interceptors using this service.
*/ */
@Deprecated @Deprecated
@ -577,6 +580,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Returns the interceptor registry for this service. Use this registry to register and unregister * Returns the interceptor registry for this service. Use this registry to register and unregister
*
* @since 3.8.0 * @since 3.8.0
*/ */
@Override @Override
@ -850,6 +854,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
requestDetails.setServletRequest(theRequest); requestDetails.setServletRequest(theRequest);
requestDetails.setServletResponse(theResponse); requestDetails.setServletResponse(theResponse);
String requestId = getOrCreateRequestId(theRequest);
requestDetails.setRequestId(requestId);
addRequestIdToResponse(requestDetails, requestId);
theRequest.setAttribute(SERVLET_CONTEXT_ATTRIBUTE, getServletContext()); theRequest.setAttribute(SERVLET_CONTEXT_ATTRIBUTE, getServletContext());
try { try {
@ -1066,6 +1074,40 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} }
} }
protected void addRequestIdToResponse(ServletRequestDetails theRequestDetails, String theRequestId) {
theRequestDetails.getResponse().addHeader(Constants.HEADER_REQUEST_ID, theRequestId);
}
/**
* Reads a requet ID from the request headers via the {@link Constants#HEADER_REQUEST_ID}
* header, or generates one if none is supplied.
* <p>
* Note that the generated request ID is a random 64-bit long integer encoded as
* hexadecimal. It is not generated using any cryptographic algorithms or a secure
* PRNG, so it should not be used for anything other than troubleshooting purposes.
* </p>
*/
protected String getOrCreateRequestId(HttpServletRequest theRequest) {
String requestId = theRequest.getHeader(Constants.HEADER_REQUEST_ID);
if (isNotBlank(requestId)) {
for (char nextChar : requestId.toCharArray()) {
if (!Character.isLetterOrDigit(nextChar)) {
if (nextChar != '.' && nextChar != '-' && nextChar != '_' && nextChar != ' ') {
requestId = null;
break;
}
}
}
}
if (isBlank(requestId)) {
requestId = Long.toHexString(RANDOM.nextLong());
requestId = leftPad(requestId, 16, '0');
}
return requestId;
}
protected void validateRequest(ServletRequestDetails theRequestDetails) { protected void validateRequest(ServletRequestDetails theRequestDetails) {
String[] elements = theRequestDetails.getParameters().get(Constants.PARAM_ELEMENTS); String[] elements = theRequestDetails.getParameters().get(Constants.PARAM_ELEMENTS);
if (elements != null) { if (elements != null) {

View File

@ -8,6 +8,7 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.server.IRestfulResponse;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.ResponseDetails; import ca.uhn.fhir.rest.api.server.ResponseDetails;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
@ -31,10 +32,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Date; import java.util.*;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.*; import static org.apache.commons.lang3.StringUtils.*;
@ -402,10 +400,7 @@ public class ResponseHighlighterInterceptor {
Enumeration<String> headerValuesEnum = sr.getHeaders(nextHeaderName); Enumeration<String> headerValuesEnum = sr.getHeaders(nextHeaderName);
while (headerValuesEnum.hasMoreElements()) { while (headerValuesEnum.hasMoreElements()) {
String nextHeaderValue = headerValuesEnum.nextElement(); String nextHeaderValue = headerValuesEnum.nextElement();
b.append("<div class=\"headersRow\">"); appendHeader(b, nextHeaderName, nextHeaderValue);
b.append("<span class=\"headerName\">").append(nextHeaderName).append(": ").append("</span>");
b.append("<span class=\"headerValue\">").append(nextHeaderValue).append("</span>");
b.append("</div>");
} }
} }
b.append("</div>"); b.append("</div>");
@ -701,14 +696,26 @@ public class ResponseHighlighterInterceptor {
nextHeaderValue = responseEncoding.getResourceContentType() + ";charset=utf-8"; nextHeaderValue = responseEncoding.getResourceContentType() + ";charset=utf-8";
} }
} }
b.append("<div class=\"headersRow\">"); appendHeader(b, nextHeaderName, nextHeaderValue);
b.append("<span class=\"headerName\">").append(nextHeaderName).append(": ").append("</span>");
b.append("<span class=\"headerValue\">").append(nextHeaderValue).append("</span>");
b.append("</div>");
} }
} }
IRestfulResponse response = theRequestDetails.getResponse();
for (Map.Entry<String, List<String>> next : response.getHeaders().entrySet()) {
String name = next.getKey();
for (String nextValue : next.getValue()) {
appendHeader(b, name, nextValue);
}
}
b.append("</div>"); b.append("</div>");
} }
} }
private void appendHeader(StringBuilder theBuilder, String theHeaderName, String theHeaderValue) {
theBuilder.append("<div class=\"headersRow\">");
theBuilder.append("<span class=\"headerName\">").append(theHeaderName).append(": ").append("</span>");
theBuilder.append("<span class=\"headerValue\">").append(theHeaderValue).append("</span>");
theBuilder.append("</div>");
}
} }

View File

@ -158,7 +158,7 @@ public class ConsentInterceptor {
continue; continue;
} }
ConsentOutcome nextOutcome = myConsentService.seeResource(theRequestDetails, nextResource, myContextConsentServices); ConsentOutcome nextOutcome = myConsentService.willSeeResource(theRequestDetails, nextResource, myContextConsentServices);
switch (nextOutcome.getStatus()) { switch (nextOutcome.getStatus()) {
case PROCEED: case PROCEED:
if (nextOutcome.getResource() != null) { if (nextOutcome.getResource() != null) {
@ -203,7 +203,7 @@ public class ConsentInterceptor {
// See outer resource // See outer resource
if (alreadySeenResources.putIfAbsent(theResource.getResponseResource(), Boolean.TRUE) == null) { if (alreadySeenResources.putIfAbsent(theResource.getResponseResource(), Boolean.TRUE) == null) {
final ConsentOutcome outcome = myConsentService.seeResource(theRequestDetails, theResource.getResponseResource(), myContextConsentServices); final ConsentOutcome outcome = myConsentService.willSeeResource(theRequestDetails, theResource.getResponseResource(), myContextConsentServices);
if (outcome.getResource() != null) { if (outcome.getResource() != null) {
theResource.setResponseResource(outcome.getResource()); theResource.setResponseResource(outcome.getResource());
} }
@ -245,7 +245,7 @@ public class ConsentInterceptor {
if (alreadySeenResources.putIfAbsent((IBaseResource) theElement, Boolean.TRUE) != null) { if (alreadySeenResources.putIfAbsent((IBaseResource) theElement, Boolean.TRUE) != null) {
return true; return true;
} }
ConsentOutcome childOutcome = myConsentService.seeResource(theRequestDetails, (IBaseResource) theElement, myContextConsentServices); ConsentOutcome childOutcome = myConsentService.willSeeResource(theRequestDetails, (IBaseResource) theElement, myContextConsentServices);
IBaseResource replacementResource = null; IBaseResource replacementResource = null;
boolean shouldReplaceResource = false; boolean shouldReplaceResource = false;

View File

@ -44,8 +44,8 @@ public class DelegatingConsentService implements IConsentService {
} }
@Override @Override
public ConsentOutcome seeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
return myTarget.seeResource(theRequestDetails, theResource ,theContextServices); return myTarget.willSeeResource(theRequestDetails, theResource ,theContextServices);
} }
@Override @Override

View File

@ -51,7 +51,7 @@ public interface IConsentService {
* <p> * <p>
* Implementations should make no attempt to modify the returned result within * Implementations should make no attempt to modify the returned result within
* this method. For modification use cases (e.g. masking for consent rules) the * this method. For modification use cases (e.g. masking for consent rules) the
* user should use the {@link #seeResource(RequestDetails, IBaseResource, IConsentContextServices)} * user should use the {@link #willSeeResource(RequestDetails, IBaseResource, IConsentContextServices)}
* method to actually make changes. This method is intended to only * method to actually make changes. This method is intended to only
* to make decisions. * to make decisions.
* </p> * </p>
@ -76,7 +76,7 @@ public interface IConsentService {
ConsentOutcome canSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices); ConsentOutcome canSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices);
/** /**
* This method is called if a user may potentially see a resource, either completely * This method is called if a user is about to see a resource, either completely
* or partially. In other words, if the user is going to see any part of this resource * or partially. In other words, if the user is going to see any part of this resource
* via READ operations, SEARCH operations, etc., this method is * via READ operations, SEARCH operations, etc., this method is
* called. This method may modify the resource in order to filter/mask aspects of * called. This method may modify the resource in order to filter/mask aspects of
@ -92,7 +92,7 @@ public interface IConsentService {
* </p> * </p>
* <ul> * <ul>
* <li>{@link ConsentOperationStatusEnum#AUTHORIZED}: The resource will be returned to the client.</li> * <li>{@link ConsentOperationStatusEnum#AUTHORIZED}: The resource will be returned to the client.</li>
* <li>{@link ConsentOperationStatusEnum#PROCEED}: The resource will be returned to the client. Any embedded resources contained within the resource will also be checked by {@link #seeResource(RequestDetails, IBaseResource, IConsentContextServices)}.</li> * <li>{@link ConsentOperationStatusEnum#PROCEED}: The resource will be returned to the client. Any embedded resources contained within the resource will also be checked by {@link #willSeeResource(RequestDetails, IBaseResource, IConsentContextServices)}.</li>
* <li>{@link ConsentOperationStatusEnum#REJECT}: The resource will not be returned to the client. If the resource supplied to the </li> * <li>{@link ConsentOperationStatusEnum#REJECT}: The resource will not be returned to the client. If the resource supplied to the </li>
* </ul> * </ul>
* *
@ -108,7 +108,7 @@ public interface IConsentService {
* consent directives. * consent directives.
* @return An outcome object. See method documentation for a description. * @return An outcome object. See method documentation for a description.
*/ */
ConsentOutcome seeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices); ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices);
/** /**
* This method is called when an operation is complete. It can be used to perform * This method is called when an operation is complete. It can be used to perform

View File

@ -40,6 +40,9 @@ import ca.uhn.fhir.rest.server.RestfulResponse;
public class ServletRestfulResponse extends RestfulResponse<ServletRequestDetails> { public class ServletRestfulResponse extends RestfulResponse<ServletRequestDetails> {
/**
* Constructor
*/
public ServletRestfulResponse(ServletRequestDetails servletRequestDetails) { public ServletRestfulResponse(ServletRequestDetails servletRequestDetails) {
super(servletRequestDetails); super(servletRequestDetails);
} }

View File

@ -32,7 +32,6 @@ import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -45,8 +44,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.test.utilities.JettyUtil;
@ -364,6 +362,38 @@ public class SearchR4Test {
} }
@Test
public void testRequestIdGeneratedAndReturned() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String requestId = status.getFirstHeader(Constants.HEADER_REQUEST_ID).getValue();
assertThat(requestId, matchesPattern("[a-z0-9]{16}"));
}
}
@Test
public void testRequestIdSuppliedAndReturned() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
httpGet.addHeader(Constants.HEADER_REQUEST_ID, "help im a bug");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String requestId = status.getFirstHeader(Constants.HEADER_REQUEST_ID).getValue();
assertThat(requestId, matchesPattern("help im a bug"));
}
}
@Test
public void testRequestIdSuppliedAndReturned_Invalid() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_pretty=true");
httpGet.addHeader(Constants.HEADER_REQUEST_ID, "help i'm a bug");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String requestId = status.getFirstHeader(Constants.HEADER_REQUEST_ID).getValue();
assertThat(requestId, matchesPattern("[a-z0-9]{16}"));
}
}
@Test @Test
public void testSearchWithInvalidChain() throws Exception { public void testSearchWithInvalidChain() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar");

View File

@ -97,7 +97,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.seeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.willSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
@ -131,7 +131,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.seeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.willSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_total=accurate"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_total=accurate");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) { try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
@ -159,7 +159,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t-> ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t-> ConsentOutcome.PROCEED);
when(myConsentSvc.seeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t-> ConsentOutcome.AUTHORIZED); when(myConsentSvc.willSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t-> ConsentOutcome.AUTHORIZED);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
@ -172,7 +172,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(any(), any()); verify(myConsentSvc, times(1)).startOperation(any(), any());
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
verify(myConsentSvc, times(3)).seeResource(any(), any(), any()); verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any());
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc); verifyNoMoreInteractions(myConsentSvc);
@ -186,7 +186,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.seeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{ when(myConsentSvc.willSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{
OperationOutcome oo = new OperationOutcome(); OperationOutcome oo = new OperationOutcome();
oo.addIssue().setDiagnostics("A DIAG"); oo.addIssue().setDiagnostics("A DIAG");
return new ConsentOutcome(ConsentOperationStatusEnum.REJECT, oo); return new ConsentOutcome(ConsentOperationStatusEnum.REJECT, oo);
@ -203,7 +203,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(any(), any()); verify(myConsentSvc, times(1)).startOperation(any(), any());
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
verify(myConsentSvc, times(3)).seeResource(any(), any(), any()); verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any());
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc); verifyNoMoreInteractions(myConsentSvc);
@ -216,7 +216,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t-> ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t-> ConsentOutcome.PROCEED);
when(myConsentSvc.seeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t-> { when(myConsentSvc.willSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t-> {
return ConsentOutcome.REJECT; return ConsentOutcome.REJECT;
}); });
@ -230,7 +230,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(any(), any()); verify(myConsentSvc, times(1)).startOperation(any(), any());
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
verify(myConsentSvc, times(3)).seeResource(any(), any(), any()); // the two patients + the bundle verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any()); // the two patients + the bundle
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc); verifyNoMoreInteractions(myConsentSvc);
@ -243,7 +243,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.seeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{ when(myConsentSvc.willSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{
IBaseResource resource = (IBaseResource) t.getArguments()[1]; IBaseResource resource = (IBaseResource) t.getArguments()[1];
if ("PTA".equals(resource.getIdElement().getIdPart())) { if ("PTA".equals(resource.getIdElement().getIdPart())) {
OperationOutcome oo = new OperationOutcome(); OperationOutcome oo = new OperationOutcome();
@ -268,7 +268,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(any(), any()); verify(myConsentSvc, times(1)).startOperation(any(), any());
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
verify(myConsentSvc, times(3)).seeResource(any(), any(), any()); verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any());
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc); verifyNoMoreInteractions(myConsentSvc);
@ -281,7 +281,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.seeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{ when(myConsentSvc.willSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{
IBaseResource resource = (IBaseResource) t.getArguments()[1]; IBaseResource resource = (IBaseResource) t.getArguments()[1];
if (resource.getIdElement().getIdPart().equals("PTA")) { if (resource.getIdElement().getIdPart().equals("PTA")) {
Patient replacement = new Patient(); Patient replacement = new Patient();
@ -308,7 +308,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(any(), any()); verify(myConsentSvc, times(1)).startOperation(any(), any());
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
verify(myConsentSvc, times(4)).seeResource(any(), any(), any()); verify(myConsentSvc, times(4)).willSeeResource(any(), any(), any());
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc); verifyNoMoreInteractions(myConsentSvc);
@ -321,7 +321,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.seeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{ when(myConsentSvc.willSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{
IBaseResource resource = (IBaseResource) t.getArguments()[1]; IBaseResource resource = (IBaseResource) t.getArguments()[1];
if (resource.getIdElement().getIdPart().equals("PTA")) { if (resource.getIdElement().getIdPart().equals("PTA")) {
((Patient)resource).addIdentifier().setSystem("REPLACEMENT"); ((Patient)resource).addIdentifier().setSystem("REPLACEMENT");
@ -345,7 +345,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(any(), any()); verify(myConsentSvc, times(1)).startOperation(any(), any());
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
verify(myConsentSvc, times(3)).seeResource(any(), any(), any()); verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any());
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc); verifyNoMoreInteractions(myConsentSvc);

View File

@ -258,13 +258,15 @@ public class ResponseHighlightingInterceptorTest {
CloseableHttpResponse status = ourClient.execute(httpGet); CloseableHttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
status.close(); status.close();
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase()); assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase());
assertThat(responseContent, containsString("html")); assertThat(responseContent, containsString("html"));
assertThat(responseContent, containsString(">{<")); assertThat(responseContent, containsString(">{<"));
assertThat(responseContent, not(containsString("&lt;"))); assertThat(responseContent, not(containsString("&lt;")));
assertThat(responseContent, containsString(Constants.HEADER_REQUEST_ID));
ourLog.info(responseContent);
} }
@Test @Test

View File

@ -215,6 +215,11 @@
in the processing lifecycle if there is no chance they will be permitted in the processing lifecycle if there is no chance they will be permitted
later (i.e. because the type is not authorized at all) later (i.e. because the type is not authorized at all)
</action> </action>
<action type="add">
The HAPI FHIR server will now generate a random transaction ID to every
request and add it to the response headers. Clients may supply the transaction
header via the <![CDATA[<code>X-Request-ID</code>]]> header.
</action>
</release> </release>
<release version="3.8.0" date="2019-05-30" description="Hippo"> <release version="3.8.0" date="2019-05-30" description="Hippo">
<action type="fix"> <action type="fix">