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
*/
@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
if (theResource instanceof Observation) {
Observation obs = (Observation)theResource;

View File

@ -25,6 +25,7 @@ import java.util.*;
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_NO_CACHE = "no-cache";
public static final String CACHE_CONTROL_NO_STORE = "no-store";

View File

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

View File

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

View File

@ -37,9 +37,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -72,6 +72,7 @@ public abstract class RequestDetails {
private Map<String, List<String>> myUnqualifiedToQualifiedNames;
private Map<Object, Object> myUserData;
private IBaseResource myResource;
private String myRequestId;
/**
* Constructor
@ -80,6 +81,14 @@ public abstract class RequestDetails {
myInterceptorBroadcaster = theInterceptorBroadcaster;
}
public String getRequestId() {
return myRequestId;
}
public void setRequestId(String theRequestId) {
myRequestId = theRequestId;
}
public StopWatch getRequestStopwatch() {
return myRequestStopwatch;
}

View File

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

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -77,8 +77,7 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.*;
@SuppressWarnings("WeakerAccess")
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 Logger ourLog = LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L;
private static final Random RANDOM = new Random();
private final List<Object> myPlainProviders = new ArrayList<>();
private final List<IResourceProvider> myResourceProviders = new ArrayList<>();
private IInterceptorService myInterceptorService;
@ -171,6 +171,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
if (isNotBlank(poweredByHeader)) {
theHttpResponse.addHeader(Constants.POWERED_BY_HEADER, poweredByHeader);
}
}
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
*
* @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
@ -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
*
* @since 3.8.0
*/
@Override
@ -850,6 +854,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
requestDetails.setServletRequest(theRequest);
requestDetails.setServletResponse(theResponse);
String requestId = getOrCreateRequestId(theRequest);
requestDetails.setRequestId(requestId);
addRequestIdToResponse(requestDetails, requestId);
theRequest.setAttribute(SERVLET_CONTEXT_ATTRIBUTE, getServletContext());
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) {
String[] elements = theRequestDetails.getParameters().get(Constants.PARAM_ELEMENTS);
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.EncodingEnum;
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.ResponseDetails;
import ca.uhn.fhir.rest.server.RestfulServer;
@ -31,10 +32,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.*;
@ -402,10 +400,7 @@ public class ResponseHighlighterInterceptor {
Enumeration<String> headerValuesEnum = sr.getHeaders(nextHeaderName);
while (headerValuesEnum.hasMoreElements()) {
String nextHeaderValue = headerValuesEnum.nextElement();
b.append("<div class=\"headersRow\">");
b.append("<span class=\"headerName\">").append(nextHeaderName).append(": ").append("</span>");
b.append("<span class=\"headerValue\">").append(nextHeaderValue).append("</span>");
b.append("</div>");
appendHeader(b, nextHeaderName, nextHeaderValue);
}
}
b.append("</div>");
@ -701,14 +696,26 @@ public class ResponseHighlighterInterceptor {
nextHeaderValue = responseEncoding.getResourceContentType() + ";charset=utf-8";
}
}
b.append("<div class=\"headersRow\">");
b.append("<span class=\"headerName\">").append(nextHeaderName).append(": ").append("</span>");
b.append("<span class=\"headerValue\">").append(nextHeaderValue).append("</span>");
b.append("</div>");
appendHeader(b, nextHeaderName, nextHeaderValue);
}
}
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>");
}
}
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;
}
ConsentOutcome nextOutcome = myConsentService.seeResource(theRequestDetails, nextResource, myContextConsentServices);
ConsentOutcome nextOutcome = myConsentService.willSeeResource(theRequestDetails, nextResource, myContextConsentServices);
switch (nextOutcome.getStatus()) {
case PROCEED:
if (nextOutcome.getResource() != null) {
@ -203,7 +203,7 @@ public class ConsentInterceptor {
// See outer resource
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) {
theResource.setResponseResource(outcome.getResource());
}
@ -245,7 +245,7 @@ public class ConsentInterceptor {
if (alreadySeenResources.putIfAbsent((IBaseResource) theElement, Boolean.TRUE) != null) {
return true;
}
ConsentOutcome childOutcome = myConsentService.seeResource(theRequestDetails, (IBaseResource) theElement, myContextConsentServices);
ConsentOutcome childOutcome = myConsentService.willSeeResource(theRequestDetails, (IBaseResource) theElement, myContextConsentServices);
IBaseResource replacementResource = null;
boolean shouldReplaceResource = false;

View File

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

View File

@ -51,7 +51,7 @@ public interface IConsentService {
* <p>
* Implementations should make no attempt to modify the returned result within
* 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
* to make decisions.
* </p>
@ -76,7 +76,7 @@ public interface IConsentService {
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
* via READ operations, SEARCH operations, etc., this method is
* called. This method may modify the resource in order to filter/mask aspects of
@ -92,7 +92,7 @@ public interface IConsentService {
* </p>
* <ul>
* <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>
* </ul>
*
@ -108,7 +108,7 @@ public interface IConsentService {
* consent directives.
* @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

View File

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

View File

@ -32,7 +32,6 @@ import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@ -45,8 +44,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
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
public void testSearchWithInvalidChain() throws Exception {
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.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");
@ -131,7 +131,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(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");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
@ -159,7 +159,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(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");
@ -172,7 +172,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(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(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc);
@ -186,7 +186,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(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();
oo.addIssue().setDiagnostics("A DIAG");
return new ConsentOutcome(ConsentOperationStatusEnum.REJECT, oo);
@ -203,7 +203,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(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(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc);
@ -216,7 +216,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(any(), any())).thenReturn(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;
});
@ -230,7 +230,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(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(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc);
@ -243,7 +243,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(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];
if ("PTA".equals(resource.getIdElement().getIdPart())) {
OperationOutcome oo = new OperationOutcome();
@ -268,7 +268,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(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(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc);
@ -281,7 +281,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(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];
if (resource.getIdElement().getIdPart().equals("PTA")) {
Patient replacement = new Patient();
@ -308,7 +308,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(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(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc);
@ -321,7 +321,7 @@ public class ConsentInterceptorTest {
when(myConsentSvc.startOperation(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];
if (resource.getIdElement().getIdPart().equals("PTA")) {
((Patient)resource).addIdentifier().setSystem("REPLACEMENT");
@ -345,7 +345,7 @@ public class ConsentInterceptorTest {
verify(myConsentSvc, times(1)).startOperation(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(0)).completeOperationFailure(any(), any(), any());
verifyNoMoreInteractions(myConsentSvc);

View File

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

View File

@ -215,6 +215,11 @@
in the processing lifecycle if there is no chance they will be permitted
later (i.e. because the type is not authorized at all)
</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 version="3.8.0" date="2019-05-30" description="Hippo">
<action type="fix">