Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
James Agnew 2018-01-28 14:02:53 -06:00
commit 044b9f584a
27 changed files with 623 additions and 613 deletions

View File

@ -1,42 +0,0 @@
package ca.uhn.fhir.rest.annotation;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.TagList;
/**
* Parameter annotation for the {@link TagList} parameter in a {@link GetTags},
* {@link AddTags}, or {@link DeleteTags} method.
*
* @see GetTags
* @see AddTags
* @see DeleteTags
*/
@Target(value = ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TagListParam {
// nothing
}

View File

@ -1,7 +1,28 @@
package ca.uhn.fhir.rest.param; package ca.uhn.fhir.rest.param;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.binder.QueryParameterAndBinder;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/* /*
* #%L * #%L
* HAPI FHIR - Core Library * HAPI FHIR - Core Library
@ -21,29 +42,9 @@ import java.lang.reflect.Method;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import java.util.*;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.TagListParam;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.binder.QueryParameterAndBinder;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.UrlUtil;
public class ParameterUtil { public class ParameterUtil {
private static final String LABEL = "label=\"";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParameterUtil.class);
private static final String SCHEME = "scheme=\"";
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T extends IIdType> T convertIdToType(IIdType value, Class<T> theIdParamType) { public static <T extends IIdType> T convertIdToType(IIdType value, Class<T> theIdParamType) {
if (value != null && !theIdParamType.isAssignableFrom(value.getClass())) { if (value != null && !theIdParamType.isAssignableFrom(value.getClass())) {
@ -179,8 +180,7 @@ public class ParameterUtil {
public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) { public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) {
int paramIndex = 0; int paramIndex = 0;
for (Annotation[] annotations : theMethod.getParameterAnnotations()) { for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) { for (Annotation nextAnnotation : annotations) {
Annotation nextAnnotation = annotations[annotationIndex];
Class<? extends Annotation> class1 = nextAnnotation.annotationType(); Class<? extends Annotation> class1 = nextAnnotation.annotationType();
if (toFind.isAssignableFrom(class1)) { if (toFind.isAssignableFrom(class1)) {
return paramIndex; return paramIndex;
@ -191,10 +191,6 @@ public class ParameterUtil {
return null; return null;
} }
public static Integer findTagListParameterIndex(Method theMethod) {
return findParamAnnotationIndex(theMethod, TagListParam.class);
}
public static Object fromInteger(Class<?> theType, IntegerDt theArgument) { public static Object fromInteger(Class<?> theType, IntegerDt theArgument) {
if (theType.equals(Integer.class)) { if (theType.equals(Integer.class)) {
if (theArgument == null) { if (theArgument == null) {
@ -271,12 +267,8 @@ public class ParameterUtil {
}; };
} }
static List<String> splitParameterString(String theInput, boolean theUnescapeComponents) {
return splitParameterString(theInput, ',', theUnescapeComponents);
}
static List<String> splitParameterString(String theInput, char theDelimiter, boolean theUnescapeComponents) { static List<String> splitParameterString(String theInput, char theDelimiter, boolean theUnescapeComponents) {
ArrayList<String> retVal = new ArrayList<String>(); ArrayList<String> retVal = new ArrayList<>();
if (theInput != null) { if (theInput != null) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
for (int i = 0; i < theInput.length(); i++) { for (int i = 0; i < theInput.length(); i++) {
@ -335,7 +327,7 @@ public class ParameterUtil {
*/ */
public static String unescape(String theValue) { public static String unescape(String theValue) {
if (theValue == null) { if (theValue == null) {
return theValue; return null;
} }
if (theValue.indexOf('\\') == -1) { if (theValue.indexOf('\\') == -1) {
return theValue; return theValue;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
class EncodedResource { class EncodedResource {

View File

@ -7,6 +7,7 @@ import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.query.criteria.LiteralHandlingMode;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -133,6 +134,8 @@ public class TestR4Config extends BaseJavaConfigR4 {
extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.default.directory_provider", "ram");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.autoregister_listeners", "true"); extraProperties.put("hibernate.search.autoregister_listeners", "true");
extraProperties.put("hibernate.criteria.literal_handling_mode", LiteralHandlingMode.BIND);
return extraProperties; return extraProperties;
} }

View File

@ -49,6 +49,11 @@ public abstract class BaseJpaTest {
protected ArrayList<IServerInterceptor> myServerInterceptorList; protected ArrayList<IServerInterceptor> myServerInterceptorList;
protected IRequestOperationCallback myRequestOperationCallback = mock(IRequestOperationCallback.class); protected IRequestOperationCallback myRequestOperationCallback = mock(IRequestOperationCallback.class);
@After
public final void afterPerformCleanup() {
BaseHapiFhirResourceDao.setDisableIncrementOnUpdateForUnitTest(false);
}
@Before @Before
public void beforeCreateSrd() { public void beforeCreateSrd() {
mySrd = mock(ServletRequestDetails.class, Mockito.RETURNS_DEEP_STUBS); mySrd = mock(ServletRequestDetails.class, Mockito.RETURNS_DEEP_STUBS);
@ -58,11 +63,6 @@ public abstract class BaseJpaTest {
when(mySrd.getUserData()).thenReturn(new HashMap<>()); when(mySrd.getUserData()).thenReturn(new HashMap<>());
} }
@After
public final void afterPerformCleanup() {
BaseHapiFhirResourceDao.setDisableIncrementOnUpdateForUnitTest(false);
}
protected abstract FhirContext getContext(); protected abstract FhirContext getContext();
/** /**
@ -159,11 +159,24 @@ public abstract class BaseJpaTest {
protected List<IIdType> toUnqualifiedVersionlessIds(IBundleProvider theFound) { protected List<IIdType> toUnqualifiedVersionlessIds(IBundleProvider theFound) {
List<IIdType> retVal = new ArrayList<IIdType>(); List<IIdType> retVal = new ArrayList<IIdType>();
int size = theFound.size(); Integer size = theFound.size();
StopWatch sw = new StopWatch();
while (size == null) {
int timeout = 20000;
if (sw.getMillis() > timeout) {
fail("Waited over "+timeout+"ms for search");
}
try {
Thread.sleep(100);
} catch (InterruptedException theE) {
//ignore
}
}
ourLog.info("Found {} results", size); ourLog.info("Found {} results", size);
List<IBaseResource> resources = theFound.getResources(0, size); List<IBaseResource> resources = theFound.getResources(0, size);
for (IBaseResource next : resources) { for (IBaseResource next : resources) {
retVal.add((IIdType) next.getIdElement().toUnqualifiedVersionless()); retVal.add(next.getIdElement().toUnqualifiedVersionless());
} }
return retVal; return retVal;
} }
@ -171,7 +184,7 @@ public abstract class BaseJpaTest {
protected List<IIdType> toUnqualifiedVersionlessIds(List<IBaseResource> theFound) { protected List<IIdType> toUnqualifiedVersionlessIds(List<IBaseResource> theFound) {
List<IIdType> retVal = new ArrayList<IIdType>(); List<IIdType> retVal = new ArrayList<IIdType>();
for (IBaseResource next : theFound) { for (IBaseResource next : theFound) {
retVal.add((IIdType) next.getIdElement().toUnqualifiedVersionless()); retVal.add(next.getIdElement().toUnqualifiedVersionless());
} }
return retVal; return retVal;
} }
@ -210,7 +223,7 @@ public abstract class BaseJpaTest {
} }
@AfterClass @AfterClass
public static void afterClassShutdownDerby() throws SQLException { public static void afterClassShutdownDerby() {
// DriverManager.getConnection("jdbc:derby:;shutdown=true"); // DriverManager.getConnection("jdbc:derby:;shutdown=true");
// try { // try {
// DriverManager.getConnection("jdbc:derby:memory:myUnitTestDB;drop=true"); // DriverManager.getConnection("jdbc:derby:memory:myUnitTestDB;drop=true");

View File

@ -48,118 +48,28 @@ import ca.uhn.fhir.validation.ResultSeverityEnum;
public class SystemProviderR4Test extends BaseJpaR4Test { public class SystemProviderR4Test extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderR4Test.class);
private static RestfulServer myRestServer; private static RestfulServer myRestServer;
private static IGenericClient ourClient; private static IGenericClient ourClient;
private static FhirContext ourCtx; private static FhirContext ourCtx;
private static CloseableHttpClient ourHttpClient; private static CloseableHttpClient ourHttpClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderR4Test.class);
private static Server ourServer; private static Server ourServer;
private static String ourServerBase; private static String ourServerBase;
private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor; private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor;
@Test @SuppressWarnings("deprecation")
public void testTransactionWithInlineConditionalUrl() throws Exception { @After
myDaoConfig.setAllowInlineMatchUrlReferences(true); public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
Patient p = new Patient(); ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
p.addName().setFamily("van de Heuvelcx85ioqWJbI").addGiven("Pietercx85ioqWJbI");
myPatientDao.create(p, mySrd);
Organization o = new Organization();
o.addIdentifier().setSystem("urn:oid:2.16.840.1.113883.2.4.6.1").setValue("07-8975469");
myOrganizationDao.create(o, mySrd);
//@formatter:off
String input = "<Bundle xmlns=\"http://hl7.org/fhir\">\n" +
" <id value=\"20160113160203\"/>\n" +
" <type value=\"transaction\"/>\n" +
" <entry>\n" +
" <fullUrl value=\"urn:uuid:c72aa430-2ddc-456e-7a09-dea8264671d8\"/>\n" +
" <resource>\n" +
" <Encounter>\n" +
" <identifier>\n" +
" <use value=\"official\"/>\n" +
" <system value=\"http://healthcare.example.org/identifiers/encounter\"/>\n" +
" <value value=\"845962.8975469\"/>\n" +
" </identifier>\n" +
" <status value=\"in-progress\"/>\n" +
" <class value=\"inpatient\"/>\n" +
" <patient>\n" +
" <reference value=\"Patient?family=van%20de%20Heuvelcx85ioqWJbI&amp;given=Pietercx85ioqWJbI\"/>\n" +
" </patient>\n" +
" <serviceProvider>\n" +
" <reference value=\"Organization?identifier=urn:oid:2.16.840.1.113883.2.4.6.1|07-8975469\"/>\n" +
" </serviceProvider>\n" +
" </Encounter>\n" +
" </resource>\n" +
" <request>\n" +
" <method value=\"POST\"/>\n" +
" <url value=\"Encounter\"/>\n" +
" </request>\n" +
" </entry>\n" +
"</Bundle>";
//@formatter:off
HttpPost req = new HttpPost(ourServerBase);
req.setEntity(new StringEntity(input, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8")));
CloseableHttpResponse resp = ourHttpClient.execute(req);
try {
String encoded = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(encoded);
assertThat(encoded, containsString("transaction-response"));
} finally {
IOUtils.closeQuietly(resp.getEntity().getContent());
}
} }
@Before
@Test public void before() {
public void testTransactionDeleteWithDuplicateDeletes() throws Exception { mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor();
myDaoConfig.setAllowInlineMatchUrlReferences(true); ourClient.registerInterceptor(mySimpleHeaderInterceptor);
Patient p = new Patient();
p.addName().setFamily("van de Heuvelcx85ioqWJbI").addGiven("Pietercx85ioqWJbI");
IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
ourClient.read().resource(Patient.class).withId(id);
Bundle inputBundle = new Bundle();
inputBundle.setType(BundleType.TRANSACTION);
inputBundle.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(id.getValue());
inputBundle.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(id.getValue());
inputBundle.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?name=Pietercx85ioqWJbI");
String input = myFhirCtx.newXmlParser().encodeResourceToString(inputBundle);
HttpPost req = new HttpPost(ourServerBase + "?_pretty=true");
req.setEntity(new StringEntity(input, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8")));
CloseableHttpResponse resp = ourHttpClient.execute(req);
try {
String encoded = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(encoded);
assertThat(encoded, containsString("transaction-response"));
Bundle response = myFhirCtx.newXmlParser().parseResource(Bundle.class, encoded);
assertEquals(3, response.getEntry().size());
} finally {
IOUtils.closeQuietly(resp.getEntity().getContent());
}
try {
ourClient.read().resource(Patient.class).withId(id).execute();
fail();
} catch (ResourceGoneException e) {
// good
}
} }
@Before @Before
public void beforeStartServer() throws Exception { public void beforeStartServer() throws Exception {
if (myRestServer == null) { if (myRestServer == null) {
@ -213,101 +123,6 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
myRestServer.setPagingProvider(myPagingProvider); myRestServer.setPagingProvider(myPagingProvider);
} }
@Before
public void before() {
mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor();
ourClient.registerInterceptor(mySimpleHeaderInterceptor);
}
@SuppressWarnings("deprecation")
@After
public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
}
@SuppressWarnings("deprecation")
@Test
public void testResponseUsesCorrectContentType() throws Exception {
myRestServer.setUseBrowserFriendlyContentTypes(true);
myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
HttpGet get = new HttpGet(ourServerBase);
// get.addHeader("Accept", "application/xml, text/html");
CloseableHttpResponse http = ourHttpClient.execute(get);
assertThat(http.getFirstHeader("Content-Type").getValue(), containsString("application/fhir+json"));
}
/**
* FOrmat has changed, source is no longer valid
*/
@Test
@Ignore
public void testValidateUsingIncomingResources() throws Exception {
FhirInstanceValidator val = new FhirInstanceValidator(myValidationSupport);
RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
interceptor.addValidatorModule(val);
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
interceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
myRestServer.registerInterceptor(interceptor);
try {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/questionnaire-sdc-profile-example-ussg-fht.xml");
String bundleStr = IOUtils.toString(bundleRes, StandardCharsets.UTF_8);
HttpPost req = new HttpPost(ourServerBase);
req.setEntity(new StringEntity(bundleStr, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8")));
CloseableHttpResponse resp = ourHttpClient.execute(req);
try {
String encoded = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(encoded);
//@formatter:off
assertThat(encoded, containsString("Questionnaire/54127-6/_history/"));
//@formatter:on
for (Header next : resp.getHeaders(RequestValidatingInterceptor.DEFAULT_RESPONSE_HEADER_NAME)) {
ourLog.info(next.toString());
}
} finally {
IOUtils.closeQuietly(resp.getEntity().getContent());
}
} finally {
myRestServer.unregisterInterceptor(interceptor);
}
}
@Test
public void testEverythingReturnsCorrectFormatInPagingLink() throws Exception {
myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
myRestServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10));
ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor();
myRestServer.registerInterceptor(interceptor);
for (int i = 0; i < 11; i++) {
Patient p = new Patient();
p.addName().setFamily("Name" + i);
ourClient.create().resource(p).execute();
}
HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything");
get.addHeader("Accept", "application/xml, text/html");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
String response = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(response);
assertThat(response, containsString("_format=json"));
assertEquals(200, http.getStatusLine().getStatusCode());
} finally {
http.close();
}
myRestServer.unregisterInterceptor(interceptor);
}
@Test @Test
public void testEverythingReturnsCorrectBundleType() throws Exception { public void testEverythingReturnsCorrectBundleType() throws Exception {
myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON); myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
@ -340,6 +155,35 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
myRestServer.unregisterInterceptor(interceptor); myRestServer.unregisterInterceptor(interceptor);
} }
@Test
public void testEverythingReturnsCorrectFormatInPagingLink() throws Exception {
myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
myRestServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10));
ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor();
myRestServer.registerInterceptor(interceptor);
for (int i = 0; i < 11; i++) {
Patient p = new Patient();
p.addName().setFamily("Name" + i);
ourClient.create().resource(p).execute();
}
HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything");
get.addHeader("Accept", "application/xml, text/html");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
String response = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(response);
assertThat(response, containsString("_format=json"));
assertEquals(200, http.getStatusLine().getStatusCode());
} finally {
http.close();
}
myRestServer.unregisterInterceptor(interceptor);
}
@Test @Test
public void testEverythingType() throws Exception { public void testEverythingType() throws Exception {
HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything"); HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything");
@ -351,6 +195,12 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
} }
} }
@Test
public void testGetOperationDefinition() {
OperationDefinition op = ourClient.read(OperationDefinition.class, "-s-get-resource-counts");
assertEquals("get-resource-counts", op.getCode());
}
@Test @Test
public void testMarkResourcesForReindexing() throws Exception { public void testMarkResourcesForReindexing() throws Exception {
HttpGet get = new HttpGet(ourServerBase + "/$mark-all-resources-for-reindexing"); HttpGet get = new HttpGet(ourServerBase + "/$mark-all-resources-for-reindexing");
@ -375,6 +225,18 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
} }
@SuppressWarnings("deprecation")
@Test
public void testResponseUsesCorrectContentType() throws Exception {
myRestServer.setUseBrowserFriendlyContentTypes(true);
myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
HttpGet get = new HttpGet(ourServerBase);
// get.addHeader("Accept", "application/xml, text/html");
CloseableHttpResponse http = ourHttpClient.execute(get);
assertThat(http.getFirstHeader("Content-Type").getValue(), containsString("application/fhir+json"));
}
@Transactional(propagation = Propagation.NEVER) @Transactional(propagation = Propagation.NEVER)
@Test @Test
public void testSuggestKeywords() throws Exception { public void testSuggestKeywords() throws Exception {
@ -460,9 +322,104 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
} }
@Test @Test
public void testGetOperationDefinition() { public void testTransactionCount() throws Exception {
OperationDefinition op = ourClient.read(OperationDefinition.class, "-s-get-resource-counts"); for (int i = 0; i < 20; i++) {
assertEquals("get-resource-counts", op.getCode()); Patient p = new Patient();
p.addName().setFamily("PATIENT_" + i);
myPatientDao.create(p, mySrd);
}
Bundle req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?_summary=count");
Bundle resp = ourClient.transaction().withBundle(req).execute();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
assertEquals(1, resp.getEntry().size());
Bundle respSub = (Bundle) resp.getEntry().get(0).getResource();
assertEquals(20, respSub.getTotal());
assertEquals(0, respSub.getEntry().size());
}
@Test
public void testTransactionCreateWithPreferHeader() throws Exception {
Patient p = new Patient();
p.setActive(true);
Bundle req;
Bundle resp;
// No prefer header
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(null, resp.getEntry().get(0).getResource());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
// Prefer return=minimal
mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER);
mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_MINIMAL);
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(null, resp.getEntry().get(0).getResource());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
// Prefer return=representation
mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER);
mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_REPRESENTATION);
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(Patient.class, resp.getEntry().get(0).getResource().getClass());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
}
@Test
public void testTransactionDeleteWithDuplicateDeletes() throws Exception {
myDaoConfig.setAllowInlineMatchUrlReferences(true);
Patient p = new Patient();
p.addName().setFamily("van de Heuvelcx85ioqWJbI").addGiven("Pietercx85ioqWJbI");
IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
ourClient.read().resource(Patient.class).withId(id);
Bundle inputBundle = new Bundle();
inputBundle.setType(BundleType.TRANSACTION);
inputBundle.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(id.getValue());
inputBundle.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl(id.getValue());
inputBundle.addEntry().getRequest().setMethod(HTTPVerb.DELETE).setUrl("Patient?name=Pietercx85ioqWJbI");
String input = myFhirCtx.newXmlParser().encodeResourceToString(inputBundle);
HttpPost req = new HttpPost(ourServerBase + "?_pretty=true");
req.setEntity(new StringEntity(input, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8")));
CloseableHttpResponse resp = ourHttpClient.execute(req);
try {
String encoded = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(encoded);
assertThat(encoded, containsString("transaction-response"));
Bundle response = myFhirCtx.newXmlParser().parseResource(Bundle.class, encoded);
assertEquals(3, response.getEntry().size());
} finally {
IOUtils.closeQuietly(resp.getEntity().getContent());
}
try {
ourClient.read().resource(Patient.class).withId(id).execute();
fail();
} catch (ResourceGoneException e) {
// good
}
} }
@Test @Test
@ -473,23 +430,6 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
ourLog.info(response); ourLog.info(response);
} }
@Test
public void testTransactionWithIncompleteBundle() throws Exception {
Patient patient = new Patient();
patient.setGender(AdministrativeGender.MALE);
Bundle bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
bundle.addEntry().setResource(patient);
try {
ourClient.transaction().withBundle(bundle).prettyPrint().execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.toString(), containsString("missing or invalid HTTP Verb"));
}
}
@Test @Test
public void testTransactionFromBundle2() throws Exception { public void testTransactionFromBundle2() throws Exception {
@ -607,61 +547,118 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
} }
@Test @Test
public void testTransactionCount() throws Exception { public void testTransactionWithIncompleteBundle() throws Exception {
for (int i = 0; i < 20; i++) { Patient patient = new Patient();
Patient p = new Patient(); patient.setGender(AdministrativeGender.MALE);
p.addName().setFamily("PATIENT_" + i);
myPatientDao.create(p, mySrd); Bundle bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
bundle.addEntry().setResource(patient);
try {
ourClient.transaction().withBundle(bundle).prettyPrint().execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.toString(), containsString("missing or invalid HTTP Verb"));
} }
Bundle req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient?_summary=count");
Bundle resp = ourClient.transaction().withBundle(req).execute();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
assertEquals(1, resp.getEntry().size());
Bundle respSub = (Bundle) resp.getEntry().get(0).getResource();
assertEquals(20, respSub.getTotal());
assertEquals(0, respSub.getEntry().size());
} }
@Test @Test
public void testTransactionCreateWithPreferHeader() throws Exception { public void testTransactionWithInlineConditionalUrl() throws Exception {
myDaoConfig.setAllowInlineMatchUrlReferences(true);
Patient p = new Patient(); Patient p = new Patient();
p.setActive(true); p.addName().setFamily("van de Heuvelcx85ioqWJbI").addGiven("Pietercx85ioqWJbI");
myPatientDao.create(p, mySrd);
Bundle req; Organization o = new Organization();
Bundle resp; o.addIdentifier().setSystem("urn:oid:2.16.840.1.113883.2.4.6.1").setValue("07-8975469");
myOrganizationDao.create(o, mySrd);
// No prefer header //@formatter:off
req = new Bundle(); String input = "<Bundle xmlns=\"http://hl7.org/fhir\">\n" +
req.setType(BundleType.TRANSACTION); " <id value=\"20160113160203\"/>\n" +
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); " <type value=\"transaction\"/>\n" +
resp = ourClient.transaction().withBundle(req).execute(); " <entry>\n" +
assertEquals(null, resp.getEntry().get(0).getResource()); " <fullUrl value=\"urn:uuid:c72aa430-2ddc-456e-7a09-dea8264671d8\"/>\n" +
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); " <resource>\n" +
" <Encounter>\n" +
" <identifier>\n" +
" <use value=\"official\"/>\n" +
" <system value=\"http://healthcare.example.org/identifiers/encounter\"/>\n" +
" <value value=\"845962.8975469\"/>\n" +
" </identifier>\n" +
" <status value=\"in-progress\"/>\n" +
" <class value=\"inpatient\"/>\n" +
" <patient>\n" +
" <reference value=\"Patient?family=van%20de%20Heuvelcx85ioqWJbI&amp;given=Pietercx85ioqWJbI\"/>\n" +
" </patient>\n" +
" <serviceProvider>\n" +
" <reference value=\"Organization?identifier=urn:oid:2.16.840.1.113883.2.4.6.1|07-8975469\"/>\n" +
" </serviceProvider>\n" +
" </Encounter>\n" +
" </resource>\n" +
" <request>\n" +
" <method value=\"POST\"/>\n" +
" <url value=\"Encounter\"/>\n" +
" </request>\n" +
" </entry>\n" +
"</Bundle>";
//@formatter:off
// Prefer return=minimal HttpPost req = new HttpPost(ourServerBase);
mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER); req.setEntity(new StringEntity(input, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8")));
mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_MINIMAL);
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(null, resp.getEntry().get(0).getResource());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
// Prefer return=representation CloseableHttpResponse resp = ourHttpClient.execute(req);
mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER); try {
mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_REPRESENTATION); String encoded = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
req = new Bundle(); ourLog.info(encoded);
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); assertThat(encoded, containsString("transaction-response"));
resp = ourClient.transaction().withBundle(req).execute(); } finally {
assertEquals(Patient.class, resp.getEntry().get(0).getResource().getClass()); IOUtils.closeQuietly(resp.getEntity().getContent());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); }
}
/**
* FOrmat has changed, source is no longer valid
*/
@Test
@Ignore
public void testValidateUsingIncomingResources() throws Exception {
FhirInstanceValidator val = new FhirInstanceValidator(myValidationSupport);
RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
interceptor.addValidatorModule(val);
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
interceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
myRestServer.registerInterceptor(interceptor);
try {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/questionnaire-sdc-profile-example-ussg-fht.xml");
String bundleStr = IOUtils.toString(bundleRes, StandardCharsets.UTF_8);
HttpPost req = new HttpPost(ourServerBase);
req.setEntity(new StringEntity(bundleStr, ContentType.parse(Constants.CT_FHIR_XML + "; charset=utf-8")));
CloseableHttpResponse resp = ourHttpClient.execute(req);
try {
String encoded = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(encoded);
//@formatter:off
assertThat(encoded, containsString("Questionnaire/54127-6/_history/"));
//@formatter:on
for (Header next : resp.getHeaders(RequestValidatingInterceptor.DEFAULT_RESPONSE_HEADER_NAME)) {
ourLog.info(next.toString());
}
} finally {
IOUtils.closeQuietly(resp.getEntity().getContent());
}
} finally {
myRestServer.unregisterInterceptor(interceptor);
}
} }
@AfterClass @AfterClass

View File

@ -875,12 +875,14 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
for (int i = getInterceptors().size() - 1; i >= 0; i--) { for (int i = getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = getInterceptors().get(i); IServerInterceptor next = getInterceptors().get(i);
next.processingCompletedNormally(requestDetails); try {
next.processingCompletedNormally(requestDetails);
} catch (Throwable t) {
ourLog.error("Failure in interceptor method", t);
}
} }
if (outputStreamOrWriter != null) { IOUtils.closeQuietly(outputStreamOrWriter);
outputStreamOrWriter.close();
}
} catch (NotModifiedException | AuthenticationException e) { } catch (NotModifiedException | AuthenticationException e) {

View File

@ -1,55 +0,0 @@
package ca.uhn.fhir.rest.server.method;
import java.io.IOException;
/*
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.reflect.Method;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.AddTags;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IRestfulServer;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
class AddTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
public AddTagsMethodBinding(Method theMethod, FhirContext theContext, Object theProvider, AddTags theAnnotation) {
super(theMethod, theContext, theProvider, theAnnotation.type());
}
@Override
protected boolean isDelete() {
return false;
}
@Override
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.ADD_TAGS;
}
@Override
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
return null;
}
}

View File

@ -1,117 +0,0 @@
package ca.uhn.fhir.rest.server.method;
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.TagListParam;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.IResourceProvider;
abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding<Void> {
private Class<? extends IBaseResource> myType;
private Integer myIdParamIndex;
private String myResourceName;
private Integer myTagListParamIndex;
public BaseAddOrDeleteTagsMethodBinding(Method theMethod, FhirContext theContext, Object theProvider, Class<? extends IBaseResource> theTypeFromMethodAnnotation) {
super(theMethod, theContext, theProvider);
if (theProvider instanceof IResourceProvider) {
myType = ((IResourceProvider) theProvider).getResourceType();
} else {
myType = theTypeFromMethodAnnotation;
}
if (Modifier.isInterface(myType.getModifiers())) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' does not specify a resource type, but has an @" + IdParam.class.getSimpleName()
+ " parameter. Please specity a resource type in the method annotation on this method");
}
myResourceName = theContext.getResourceDefinition(myType).getName();
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
myTagListParamIndex = ParameterUtil.findTagListParameterIndex(theMethod);
if (myIdParamIndex == null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' does not have an @" + IdParam.class.getSimpleName() + " parameter.");
}
if (myTagListParamIndex == null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' does not have a parameter of type " + TagList.class.getSimpleName() + ", or paramater is not annotated with the @"
+ TagListParam.class.getSimpleName() + " annotation");
}
}
@Override
public String getResourceName() {
return myResourceName;
}
@Override
public RestOperationTypeEnum getRestOperationType() {
return null;
}
protected abstract boolean isDelete();
@Override
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (theRequest.getRequestType() != RequestTypeEnum.POST) {
return false;
}
if (!Constants.PARAM_TAGS.equals(theRequest.getOperation())) {
return false;
}
if (!myResourceName.equals(theRequest.getResourceName())) {
return false;
}
if (theRequest.getId() == null) {
return false;
}
if (isDelete()) {
if (Constants.PARAM_DELETE.equals(theRequest.getSecondaryOperation()) == false) {
return false;
}
} else {
if (theRequest.getSecondaryOperation() != null) {
return false;
}
}
return true;
}
}

View File

@ -494,10 +494,6 @@ public abstract class BaseMethodBinding<T> {
return new HistoryMethodBinding(theMethod, theContext, theProvider); return new HistoryMethodBinding(theMethod, theContext, theProvider);
} else if (validate != null) { } else if (validate != null) {
return new ValidateMethodBindingDstu2Plus(returnType, returnTypeFromRp, theMethod, theContext, theProvider, validate); return new ValidateMethodBindingDstu2Plus(returnType, returnTypeFromRp, theMethod, theContext, theProvider, validate);
} else if (addTags != null) {
return new AddTagsMethodBinding(theMethod, theContext, theProvider, addTags);
} else if (deleteTags != null) {
return new DeleteTagsMethodBinding(theMethod, theContext, theProvider, deleteTags);
} else if (transaction != null) { } else if (transaction != null) {
return new TransactionMethodBinding(theMethod, theContext, theProvider); return new TransactionMethodBinding(theMethod, theContext, theProvider);
} else if (operation != null) { } else if (operation != null) {

View File

@ -1,55 +0,0 @@
package ca.uhn.fhir.rest.server.method;
import java.io.IOException;
/*
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.reflect.Method;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.DeleteTags;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IRestfulServer;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
public class DeleteTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
public DeleteTagsMethodBinding(Method theMethod, FhirContext theContext, Object theProvider, DeleteTags theDeleteTags) {
super(theMethod, theContext, theProvider, theDeleteTags.type());
}
@Override
protected boolean isDelete() {
return true;
}
@Override
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.DELETE_TAGS;
}
@Override
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
return null;
}
}

View File

@ -6,6 +6,7 @@ import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@ -272,6 +273,29 @@ public class ServerMimetypeR4Test {
assertEquals(Constants.CT_FHIR_XML_NEW, status.getFirstHeader("content-type").getValue().replaceAll(";.*", "")); assertEquals(Constants.CT_FHIR_XML_NEW, status.getFirstHeader("content-type").getValue().replaceAll(";.*", ""));
} }
/**
* See #837
*/
@Test
public void testResponseContentTypes() throws IOException {
assertEquals("application/fhir+json", readAndReturnContentType("application/fhir+json"));
assertEquals("application/fhir+xml", readAndReturnContentType(null));
assertEquals("application/json+fhir", readAndReturnContentType("application/json+fhir"));
assertEquals("application/json+fhir", readAndReturnContentType("application/json"));
assertEquals("application/fhir+json", readAndReturnContentType("application/fhir+json,application/json;q=0.9"));
}
private String readAndReturnContentType(String theAccept) throws IOException {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
if (theAccept != null) {
httpGet.addHeader(Constants.HEADER_ACCEPT, theAccept);
}
HttpResponse status = ourClient.execute(httpGet);
String contentType = status.getEntity().getContentType().getValue();
IOUtils.closeQuietly(status.getEntity().getContent());
contentType = contentType.replaceAll(";.*","");
return contentType;
}
@Test @Test

View File

@ -0,0 +1,189 @@
package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.api.AddProfileTagEnum;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum;
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
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.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.*;
public class InterceptorThrowingExceptionR4Test {
private static final String ERR403 = "{\"resourceType\":\"OperationOutcome\",\"issue\":[{\"severity\":\"error\",\"code\":\"processing\",\"diagnostics\":\"Access denied by default policy (no applicable rules)\"}]}";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InterceptorThrowingExceptionR4Test.class);
private static CloseableHttpClient ourClient;
private static String ourConditionalCreateId;
private static FhirContext ourCtx = FhirContext.forR4();
private static boolean ourHitMethod;
private static int ourPort;
private static List<Resource> ourReturn;
private static Server ourServer;
private static RestfulServer ourServlet;
@Before
public void before() {
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.NEVER);
for (IServerInterceptor next : new ArrayList<>(ourServlet.getInterceptors())) {
ourServlet.unregisterInterceptor(next);
}
ourServlet.setTenantIdentificationStrategy(null);
ourReturn = null;
ourHitMethod = false;
ourConditionalCreateId = "1123";
}
private Resource createPatient(Integer theId) {
Patient retVal = new Patient();
if (theId != null) {
retVal.setId(new IdType("Patient", (long) theId));
}
retVal.addName().setFamily("FAM");
return retVal;
}
private String extractResponseAndClose(HttpResponse status) throws IOException {
if (status.getEntity() == null) {
return null;
}
String responseContent;
responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
return responseContent;
}
@Test
public void testFailureInProcessingCompletedNormally() throws Exception {
final List<Integer> hit = new ArrayList<>();
ourServlet.registerInterceptor(new InterceptorAdapter(){
@Override
public void processingCompletedNormally(ServletRequestDetails theRequestDetails) {
hit.add(1);
throw new NullPointerException();
}
});
ourServlet.registerInterceptor(new InterceptorAdapter(){
@Override
public void processingCompletedNormally(ServletRequestDetails theRequestDetails) {
hit.add(2);
throw new NullPointerException();
}
});
ourServlet.registerInterceptor(new InterceptorAdapter(){
@Override
public void processingCompletedNormally(ServletRequestDetails theRequestDetails) {
hit.add(3);
throw new NullPointerException();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.singletonList(createPatient(2));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(response, containsString("FAM"));
assertTrue(ourHitMethod);
ourLog.info("Hit: {}", hit);
assertThat(hit, contains(3,2,1));
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
try {
ourServer = new Server(ourPort);
} catch (Exception e) {
ourLog.error("FAILED", e);
throw e;
}
DummyPatientResourceProvider patProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
ourServlet = new RestfulServer(ourCtx);
ourServlet.setFhirContext(ourCtx);
ourServlet.setResourceProviders(patProvider);
ServletHolder servletHolder = new ServletHolder(ourServlet);
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();
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@Read(version = true)
public Patient read(@IdParam IdType theId) {
ourHitMethod = true;
return (Patient) ourReturn.get(0);
}
}
}

33
pom.xml
View File

@ -409,8 +409,9 @@
<!-- Dependency Versions --> <!-- Dependency Versions -->
<derby_version>10.14.1.0</derby_version> <derby_version>10.14.1.0</derby_version>
<jersey_version>2.25.1</jersey_version> <jersey_version>2.25.1</jersey_version>
<jetty_version>9.4.6.v20170531</jetty_version> <jetty_version>9.4.8.v20171121</jetty_version>
<hibernate_version>5.2.10.Final</hibernate_version> <!--<hibernate_version>5.2.10.Final</hibernate_version>-->
<hibernate_version>5.2.12.Final</hibernate_version>
<hibernate_validator_version>5.4.1.Final</hibernate_validator_version> <hibernate_validator_version>5.4.1.Final</hibernate_validator_version>
<!-- Update lucene version when you update hibernate-search version --> <!-- Update lucene version when you update hibernate-search version -->
<hibernate_search_version>5.7.1.Final</hibernate_search_version> <hibernate_search_version>5.7.1.Final</hibernate_search_version>
@ -419,9 +420,9 @@
<maven_license_plugin_version>1.8</maven_license_plugin_version> <maven_license_plugin_version>1.8</maven_license_plugin_version>
<phloc_schematron_version>2.7.1</phloc_schematron_version> <phloc_schematron_version>2.7.1</phloc_schematron_version>
<phloc_commons_version>4.4.11</phloc_commons_version> <phloc_commons_version>4.4.11</phloc_commons_version>
<spring_version>5.0.0.RELEASE</spring_version> <spring_version>5.0.3.RELEASE</spring_version>
<spring-boot.version>1.5.6.RELEASE</spring-boot.version> <spring-boot.version>1.5.6.RELEASE</spring-boot.version>
<thymeleaf-version>3.0.7.RELEASE</thymeleaf-version> <thymeleaf-version>3.0.9.RELEASE</thymeleaf-version>
<!-- We are aiming to still work on a very old version of SLF4j even though we depend on the newest, just to be nice to users of the API. This version is tested in the hapi-fhir-cobertura. --> <!-- We are aiming to still work on a very old version of SLF4j even though we depend on the newest, just to be nice to users of the API. This version is tested in the hapi-fhir-cobertura. -->
<slf4j_target_version>1.6.0</slf4j_target_version> <slf4j_target_version>1.6.0</slf4j_target_version>
@ -798,6 +799,21 @@
<artifactId>jetty-servlets</artifactId> <artifactId>jetty-servlets</artifactId>
<version>${jetty_version}</version> <version>${jetty_version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-io</artifactId>
<version>${jetty_version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId>
<version>${jetty_version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty_version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId> <artifactId>jetty-servlet</artifactId>
@ -818,6 +834,11 @@
<artifactId>jetty-webapp</artifactId> <artifactId>jetty-webapp</artifactId>
<version>${jetty_version}</version> <version>${jetty_version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-xml</artifactId>
<version>${jetty_version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.websocket</groupId> <groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-api</artifactId> <artifactId>websocket-api</artifactId>
@ -1114,7 +1135,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version> <version>3.0.2</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -1210,7 +1231,7 @@
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId> <artifactId>versions-maven-plugin</artifactId>
<version>2.2</version> <version>2.5</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>

View File

@ -8,6 +8,17 @@
<body> <body>
<release version="3.3.0" date="TBD"> <release version="3.3.0" date="TBD">
<action type="add"> <action type="add">
The version of a few dependencies have been bumped to the
latest versions (dependent HAPI modules listed in brackets):
<![CDATA[
<ul>
<li>Hibernate (JPA): 5.2.10.Final -&gt; 5.2.12.Final</li>
<li>Spring (JPA): 5.0.0 -&gt; 5.0.3</li>
<li>Thymeleaf (Web Tespage Overlay): 3.0.7.RELEASE -&gt; 3.0.9.RELEASE</li>
</ul>
]]>
</action>
<action type="add">
This release corrects an inefficiency in the JPA Server, but requires a schema This release corrects an inefficiency in the JPA Server, but requires a schema
change in order to update. Prior to this version of HAPI FHIR, a CLOB column change in order to update. Prior to this version of HAPI FHIR, a CLOB column
containing the complete resource body was stored in two containing the complete resource body was stored in two
@ -32,6 +43,17 @@
Fix an issue where the JPA server crashed while attempting to normalize string values Fix an issue where the JPA server crashed while attempting to normalize string values
containing Korean text. Thanks to GitHub user @JoonggeonLee for reporting! containing Korean text. Thanks to GitHub user @JoonggeonLee for reporting!
</action> </action>
<action type="fix">
An issue was solved where it was possible for server interceptors
to have both processingCompletedNormally and handleException called
if the stream.close() method threw an exception. Thanks to Carlos
Eduardo Lara Augusto for investigating!
</action>
<action type="remove" issue="831">
The <![CDATA[<code>@TagListParam</code>]]> annotation has been removed. This
annotation had no use after DSTU1 but never got deleted and was misleading. Thanks
to Angelo Kastroulis for reporting!
</action>
<action type="add"> <action type="add">
A new method overload has been added to IServerInterceptor: A new method overload has been added to IServerInterceptor:
<![CDATA[ <![CDATA[