From 6551eb0a4eb5d640d81abdf2439dea24a3047b15 Mon Sep 17 00:00:00 2001 From: Alvin Leonard Date: Wed, 11 Oct 2017 13:40:03 +1100 Subject: [PATCH 01/39] Fix deleteByUrl to respect InCompartment Authorization Moved the assignment of the resource to delete before the actual delete as it will be used by the authorization to determine if this resource is in the compartment. --- .../java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java | 2 +- .../AuthorizationInterceptorResourceProviderDstu3Test.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 2c483a06092..46357a02595 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -255,6 +255,7 @@ public abstract class BaseHapiFhirResourceDao extends B deletedResources.add(entity); validateOkToDelete(deleteConflicts, entity); + T resourceToDelete = toResource(myResourceType, entity, false); // Notify interceptors IdDt idToDelete = entity.getIdDt(); @@ -268,7 +269,6 @@ public abstract class BaseHapiFhirResourceDao extends B updateEntity(null, entity, updateTime, updateTime); // Notify JPA interceptors - T resourceToDelete = toResource(myResourceType, entity, false); if (theRequestDetails != null) { theRequestDetails.getRequestOperationCallback().resourceDeleted(resourceToDelete); ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, idToDelete.getResourceType(), idToDelete); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/AuthorizationInterceptorResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/AuthorizationInterceptorResourceProviderDstu3Test.java index cddc9ccd802..48c68698726 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/AuthorizationInterceptorResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/AuthorizationInterceptorResourceProviderDstu3Test.java @@ -84,7 +84,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou /** - * See #503 + * See #503 #751 */ @Test public void testDeleteIsAllowedForCompartment() { @@ -99,6 +99,9 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou obsInCompartment.getSubject().setReferenceElement(id.toUnqualifiedVersionless()); IIdType obsInCompartmentId = ourClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless(); + // create a 2nd observation to be deleted by url Observation?patient=id + ourClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless(); + Observation obsNotInCompartment = new Observation(); obsNotInCompartment.setStatus(ObservationStatus.FINAL); IIdType obsNotInCompartmentId = ourClient.create().resource(obsNotInCompartment).execute().getId().toUnqualifiedVersionless(); @@ -115,6 +118,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou }); ourClient.delete().resourceById(obsInCompartmentId.toUnqualifiedVersionless()).execute(); + ourClient.delete().resourceConditionalByUrl("Observation?patient=" + id.toUnqualifiedVersionless()).execute(); try { ourClient.delete().resourceById(obsNotInCompartmentId.toUnqualifiedVersionless()).execute(); From 2114dd77062bf023e25d53ad7629652f4e1db719 Mon Sep 17 00:00:00 2001 From: Alvin Leonard Date: Wed, 11 Oct 2017 15:09:41 +1100 Subject: [PATCH 02/39] We need to set the resource id to the entity id after the update entity. --- .../main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 46357a02595..6efe7f3efed 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -254,8 +254,8 @@ public abstract class BaseHapiFhirResourceDao extends B ResourceTable entity = myEntityManager.find(ResourceTable.class, pid); deletedResources.add(entity); - validateOkToDelete(deleteConflicts, entity); T resourceToDelete = toResource(myResourceType, entity, false); + validateOkToDelete(deleteConflicts, entity); // Notify interceptors IdDt idToDelete = entity.getIdDt(); @@ -267,6 +267,7 @@ public abstract class BaseHapiFhirResourceDao extends B // Perform delete Date updateTime = new Date(); updateEntity(null, entity, updateTime, updateTime); + resourceToDelete.setId(entity.getIdDt()); // Notify JPA interceptors if (theRequestDetails != null) { From 1a7e43d2a8a2cec0b036b1eff593b97be75ce5db Mon Sep 17 00:00:00 2001 From: David Conlan Date: Tue, 17 Oct 2017 15:07:51 +1000 Subject: [PATCH 03/39] Make sure we actually have a type associated with the resourceId or we get an exception --- .../ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java index 65603cb9198..a8b8e113053 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java @@ -225,7 +225,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ { return null; } } - if (appliesToResourceId != null) { + if (appliesToResourceId != null && appliesToResourceId.hasResourceType()) { Class type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass(); if (myAppliesToTypes.contains(type) == false) { return null; From a546c94ccb65728b50bb976697584dbd0b863d3b Mon Sep 17 00:00:00 2001 From: Clayton Bodendein Date: Sat, 21 Oct 2017 23:00:19 -0500 Subject: [PATCH 04/39] Fixes #764 to allow the AbstractJaxRsConformanceProvider to support the DSTU2_HL7ORG FhirContext --- hapi-fhir-jaxrsserver-base/pom.xml | 5 + .../AbstractJaxRsConformanceProvider.java | 9 ++ ...xRsConformanceProviderDstu2Hl7OrgTest.java | 109 +++++++++++++++ ...xRsMockPatientRestProviderDstu2Hl7Org.java | 128 ++++++++++++++++++ .../conf/ServerConformanceProvider.java | 54 ++++---- 5 files changed, 276 insertions(+), 29 deletions(-) create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2Hl7OrgTest.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu2Hl7Org.java diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 3f2ac2eacae..b8d880ffb28 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -51,6 +51,11 @@ hapi-fhir-structures-dstu2 ${project.version} + + ca.uhn.hapi.fhir + hapi-fhir-structures-hl7org-dstu2 + ${project.version} + ca.uhn.hapi.fhir hapi-fhir-structures-dstu3 diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java index 0d9c7b2b1c5..22b82df6e86 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java @@ -68,6 +68,7 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv /** the conformance. It is created once during startup */ private CapabilityStatement myDstu3CapabilityStatement; private ca.uhn.fhir.model.dstu2.resource.Conformance myDstu2Conformance; + private org.hl7.fhir.instance.model.Conformance myDstu2Hl7OrgConformance; /** * Constructor allowing the description, servername and server to be set @@ -132,6 +133,10 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider serverCapabilityStatementProvider = new ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider(serverConfiguration); serverCapabilityStatementProvider.initializeOperations(); myDstu2Conformance = serverCapabilityStatementProvider.getServerConformance(null); + } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2_HL7ORG)) { + org.hl7.fhir.instance.conf.ServerConformanceProvider serverCapabilityStatementProvider = new org.hl7.fhir.instance.conf.ServerConformanceProvider(serverConfiguration); + serverCapabilityStatementProvider.initializeOperations(); + myDstu2Hl7OrgConformance = serverCapabilityStatementProvider.getServerConformance(null); } } @@ -173,6 +178,8 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { conformance = myDstu2Conformance; // return (Response) response.returnResponse(ParseAction.create(myDstu2CapabilityStatement), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName()); + } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2_HL7ORG)) { + conformance = myDstu2Hl7OrgConformance; } if (conformance != null) { @@ -261,6 +268,8 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv return Class.class.cast(CapabilityStatement.class); } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { return Class.class.cast(ca.uhn.fhir.model.dstu2.resource.Conformance.class); + } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2_HL7ORG)) { + return Class.class.cast(org.hl7.fhir.instance.model.Conformance.class); } return null; } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2Hl7OrgTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2Hl7OrgTest.java new file mode 100644 index 00000000000..9b3a8ca6fda --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2Hl7OrgTest.java @@ -0,0 +1,109 @@ +package ca.uhn.fhir.jaxrs.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.*; + +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.server.ContainerRequest; +import org.junit.Before; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProviderDstu2Hl7Org; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.server.IResourceProvider; + +public class AbstractJaxRsConformanceProviderDstu2Hl7OrgTest { + + private static final String BASEURI = "http://basiuri"; + private static final String REQUESTURI = BASEURI + "/metadata"; + AbstractJaxRsConformanceProvider provider; + private ConcurrentHashMap, IResourceProvider> providers; + private ContainerRequest headers; + private MultivaluedHashMap queryParameters; + + @Before + public void setUp() throws Exception { + // headers + headers = new ContainerRequest(new URI(BASEURI), new URI(REQUESTURI), HttpMethod.GET, null, + new MapPropertiesDelegate()); + // uri info + queryParameters = new MultivaluedHashMap(); + + + providers = new ConcurrentHashMap, IResourceProvider>(); + provider = createConformanceProvider(providers); + } + + @Test + public void testConformance() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); + Response response = createConformanceProvider(providers).conformance(); + System.out.println(response); + } + + @Test + public void testConformanceUsingOptions() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); + Response response = createConformanceProvider(providers).conformanceUsingOptions(); + System.out.println(response); + } + + @Test + public void testConformanceWithMethods() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsMockPatientRestProviderDstu2Hl7Org.class, new TestJaxRsMockPatientRestProviderDstu2Hl7Org()); + Response response = createConformanceProvider(providers).conformance(); + assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); + assertTrue(response.getEntity().toString().contains("\"type\": \"Patient\"")); + assertTrue(response.getEntity().toString().contains("someCustomOperation")); + System.out.println(response); + System.out.println(response.getEntity()); + } + + @Test + public void testConformanceInXml() throws Exception { + queryParameters.put(Constants.PARAM_FORMAT, Arrays.asList(Constants.CT_XML)); + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsMockPatientRestProviderDstu2Hl7Org.class, new TestJaxRsMockPatientRestProviderDstu2Hl7Org()); + Response response = createConformanceProvider(providers).conformance(); + assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); + System.out.println(response.getEntity()); + assertTrue(response.getEntity().toString().contains(" ")); + assertTrue(response.getEntity().toString().contains("someCustomOperation")); + System.out.println(response.getEntity()); + } + + private AbstractJaxRsConformanceProvider createConformanceProvider(final ConcurrentHashMap, IResourceProvider> providers) + throws Exception { + AbstractJaxRsConformanceProvider result = new AbstractJaxRsConformanceProvider(FhirContext.forDstu2Hl7Org(), null, null, null) { + @Override + protected ConcurrentHashMap, IResourceProvider> getProviders() { + return providers; + } + }; + // mocks + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getQueryParameters()).thenReturn(queryParameters); + when(uriInfo.getBaseUri()).thenReturn(new URI(BASEURI)); + when(uriInfo.getRequestUri()).thenReturn(new URI(BASEURI + "/foo")); + result.setUriInfo(uriInfo); + result.setHeaders(headers); + result.setUpPostConstruct(); + return result; + } + +} + diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu2Hl7Org.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu2Hl7Org.java new file mode 100644 index 00000000000..fae37282aa7 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu2Hl7Org.java @@ -0,0 +1,128 @@ +package ca.uhn.fhir.jaxrs.server.test; + +import java.util.List; + +import javax.ejb.Stateless; +import javax.interceptor.Interceptors; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.hl7.fhir.instance.model.*; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.mockito.Mockito; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.IPagingProvider; + +/** + * A test server delegating each call to a mock + */ +@Path(TestJaxRsMockPatientRestProviderDstu2Hl7Org.PATH) +@Stateless +@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML, Constants.CT_FHIR_JSON_NEW, Constants.CT_FHIR_XML_NEW }) +@Interceptors(JaxRsExceptionInterceptor.class) +public class TestJaxRsMockPatientRestProviderDstu2Hl7Org extends AbstractJaxRsResourceProvider { + + static final String PATH = "/Patient"; + + public static final TestJaxRsMockPatientRestProviderDstu2Hl7Org mock = Mockito.mock(TestJaxRsMockPatientRestProviderDstu2Hl7Org.class); + + public static final FifoMemoryPagingProvider PAGING_PROVIDER; + + static + { + PAGING_PROVIDER = new FifoMemoryPagingProvider(10); + PAGING_PROVIDER.setDefaultPageSize(10); + PAGING_PROVIDER.setMaximumPageSize(100); + } + + /** + * Constructor + */ + public TestJaxRsMockPatientRestProviderDstu2Hl7Org() { + super(FhirContext.forDstu2Hl7Org()); + } + + @Search + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name, @RequiredParam(name=Patient.SP_ADDRESS) StringAndListParam theAddressParts) { + return mock.search(name, theAddressParts); + } + + @Update + public MethodOutcome update(@IdParam final IdType theId, @ResourceParam final Patient patient,@ConditionalUrlParam final String theConditional) throws Exception { + return mock.update(theId, patient, theConditional); + } + + @Read + public Patient find(@IdParam final IdType theId) { + return mock.find(theId); + } + + @Read(version = true) + public Patient findHistory(@IdParam final IdType theId) { + return mock.findHistory(theId); + } + + @Create + public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) + throws Exception { + return mock.create(patient, theConditional); + } + + @Delete + public MethodOutcome delete(@IdParam final IdType theId, @ConditionalUrlParam final String theConditional) { + return mock.delete(theId, theConditional); + } + + @Search(compartmentName = "Condition") + public List searchCompartment(@IdParam IdType thePatientId) { + return mock.searchCompartment(thePatientId); + } + + @GET + @Path("/{id}/$someCustomOperation") + public Response someCustomOperationUsingGet(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.GET, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); + } + + @POST + @Path("/{id}/$someCustomOperation") + public Response someCustomOperationUsingPost(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.POST, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); + } + + @Operation(name = "someCustomOperation", idempotent = true, returnParameters = { + @OperationParam(name = "return", type = StringType.class) }) + public Parameters someCustomOperation(@IdParam IdType myId, @OperationParam(name = "dummy") StringType dummyInput) { + return mock.someCustomOperation(myId, dummyInput); + } + + @Validate() + public MethodOutcome validate(@ResourceParam final Patient resource) { + MethodOutcome mO = new MethodOutcome(); + mO.setOperationOutcome(new OperationOutcome()); + return mO; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Override + public IPagingProvider getPagingProvider() { + return PAGING_PROVIDER; + } + +} diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java index 858e84f87cf..9f69509733b 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java @@ -28,6 +28,7 @@ import java.util.*; import java.util.Map.Entry; import java.util.jar.Manifest; +import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import ca.uhn.fhir.context.FhirVersionEnum; @@ -70,11 +71,15 @@ public class ServerConformanceProvider implements IServerConformanceProvider myOperationBindingToName; private HashMap> myOperationNameToBindings; private String myPublisher = "Not provided"; - private RestfulServer myRestfulServer; + private RestulfulServerConfiguration myServerConfiguration; public ServerConformanceProvider(RestfulServer theRestfulServer) { - myRestfulServer = theRestfulServer; + this.myServerConfiguration = theRestfulServer.createConfiguration(); } + + public ServerConformanceProvider(RestulfulServerConfiguration theServerConfiguration) { + this.myServerConfiguration = theServerConfiguration; + } /* * Add a no-arg constructor and seetter so that the @@ -85,11 +90,16 @@ public class ServerConformanceProvider implements IServerConformanceProvider systemOps, BaseMethodBinding nextMethodBinding) { if (nextMethodBinding.getRestOperationType() != null) { @@ -114,7 +124,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider>> collectMethodBindings() { Map>> resourceToMethods = new TreeMap>>(); - for (ResourceBinding next : myRestfulServer.getResourceBindings()) { + for (ResourceBinding next : myServerConfiguration.getResourceBindings()) { String resourceName = next.getResourceName(); for (BaseMethodBinding nextMethodBinding : next.getMethodBindings()) { if (resourceToMethods.containsKey(resourceName) == false) { @@ -123,7 +133,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider nextMethodBinding : myRestfulServer.getServerBindings()) { + for (BaseMethodBinding nextMethodBinding : myServerConfiguration.getServerBindings()) { String resourceName = ""; if (resourceToMethods.containsKey(resourceName) == false) { resourceToMethods.put(resourceName, new ArrayList>()); @@ -163,10 +173,10 @@ public class ServerConformanceProvider implements IServerConformanceProvider resourceOps = new HashSet(); ConformanceRestResourceComponent resource = rest.addResource(); String resourceName = nextEntry.getKey(); - RuntimeResourceDefinition def = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); + RuntimeResourceDefinition def = myServerConfiguration.getFhirContext().getResourceDefinition(resourceName); resource.getTypeElement().setValue(def.getName()); - resource.getProfile() - .setReference((def.getResourceProfile(myRestfulServer.getServerBaseForRequest(theRequest)))); + ServletContext servletContext = (ServletContext) (theRequest == null ? null : theRequest.getAttribute(RestfulServer.SERVLET_CONTEXT_ATTRIBUTE)); + String serverBase = myServerConfiguration.getServerAddressStrategy().determineServerBase(servletContext, theRequest); + resource.getProfile().setReference((def.getResourceProfile(serverBase))); TreeSet includes = new TreeSet(); @@ -296,7 +307,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes, DynamicSearchMethodBinding searchMethodBinding) { includes.addAll(searchMethodBinding.getIncludes()); @@ -433,7 +429,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider nextTarget : nextParameter.getDeclaredTypes()) { - RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget); + RuntimeResourceDefinition targetDef = myServerConfiguration.getFhirContext().getResourceDefinition(nextTarget); if (targetDef != null) { ResourceType code; try { From b0154139617456eeb815511b68e2b8584286180c Mon Sep 17 00:00:00 2001 From: Clayton Bodendein Date: Sun, 22 Oct 2017 13:48:02 -0500 Subject: [PATCH 05/39] Fixes issue #768 so that when DataFormatExceptions are thrown when using AbstractJaxRsProviders, the stacktrace will be included in the OperationOutcome details only if AbstractJaxRsProvider#withStrackTrace() is configured to return true --- .../java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index 5ba4d6a3a54..632a42935c8 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -81,7 +81,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { private IBaseOperationOutcome createOutcome(final DataFormatException theException) { final IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getFhirContext()); - final String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException); + final String detailsValue = theException.getMessage() + (this.withStackTrace() ? "\n\n" + ExceptionUtils.getStackTrace(theException) : ""); OperationOutcomeUtil.addIssue(getFhirContext(), oo, ERROR, detailsValue, null, PROCESSING); return oo; } From 452f6bc24c1edfb5f1af9a9fc1c8eb8469d31f43 Mon Sep 17 00:00:00 2001 From: Clayton Bodendein Date: Sun, 22 Oct 2017 18:19:10 -0500 Subject: [PATCH 06/39] Related to issue #764 - adds support for the DSTU2_1 FHIR context in AbstractJaxRsConformanceProvider --- hapi-fhir-jaxrsserver-base/pom.xml | 5 + .../AbstractJaxRsConformanceProvider.java | 9 ++ ...ctJaxRsConformanceProviderDstu2_1Test.java | 108 +++++++++++++++ ...stJaxRsMockPatientRestProviderDstu2_1.java | 128 ++++++++++++++++++ .../conf/ServerConformanceProvider.java | 11 +- 5 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2_1Test.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu2_1.java diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index b8d880ffb28..3f728fccc8e 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -56,6 +56,11 @@ hapi-fhir-structures-hl7org-dstu2 ${project.version} + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu2.1 + ${project.version} + ca.uhn.hapi.fhir hapi-fhir-structures-dstu3 diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java index 22b82df6e86..6f76e3f6959 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java @@ -67,6 +67,7 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv /** the conformance. It is created once during startup */ private CapabilityStatement myDstu3CapabilityStatement; + private org.hl7.fhir.dstu2016may.model.Conformance myDstu2_1Conformance; private ca.uhn.fhir.model.dstu2.resource.Conformance myDstu2Conformance; private org.hl7.fhir.instance.model.Conformance myDstu2Hl7OrgConformance; @@ -129,6 +130,10 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv ServerCapabilityStatementProvider serverCapabilityStatementProvider = new ServerCapabilityStatementProvider(serverConfiguration); serverCapabilityStatementProvider.initializeOperations(); myDstu3CapabilityStatement = serverCapabilityStatementProvider.getServerConformance(null); + } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2_1)) { + org.hl7.fhir.dstu2016may.hapi.rest.server.ServerConformanceProvider serverCapabilityStatementProvider = new org.hl7.fhir.dstu2016may.hapi.rest.server.ServerConformanceProvider(serverConfiguration); + serverCapabilityStatementProvider.initializeOperations(); + myDstu2_1Conformance = serverCapabilityStatementProvider.getServerConformance(null); } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider serverCapabilityStatementProvider = new ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider(serverConfiguration); serverCapabilityStatementProvider.initializeOperations(); @@ -175,6 +180,8 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { conformance = myDstu3CapabilityStatement; // return (Response) response.returnResponse(ParseAction.create(myDstu3CapabilityStatement), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName()); + } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2_1)) { + conformance = myDstu2_1Conformance; } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { conformance = myDstu2Conformance; // return (Response) response.returnResponse(ParseAction.create(myDstu2CapabilityStatement), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName()); @@ -266,6 +273,8 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv public Class getResourceType() { if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { return Class.class.cast(CapabilityStatement.class); + } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2_1)) { + return Class.class.cast(org.hl7.fhir.dstu2016may.model.Conformance.class); } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { return Class.class.cast(ca.uhn.fhir.model.dstu2.resource.Conformance.class); } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2_HL7ORG)) { diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2_1Test.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2_1Test.java new file mode 100644 index 00000000000..60b447e0ab4 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2_1Test.java @@ -0,0 +1,108 @@ +package ca.uhn.fhir.jaxrs.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.*; + +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProviderDstu2_1; +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.server.ContainerRequest; +import org.junit.Before; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.server.IResourceProvider; + +public class AbstractJaxRsConformanceProviderDstu2_1Test { + + private static final String BASEURI = "http://basiuri"; + private static final String REQUESTURI = BASEURI + "/metadata"; + AbstractJaxRsConformanceProvider provider; + private ConcurrentHashMap, IResourceProvider> providers; + private ContainerRequest headers; + private MultivaluedHashMap queryParameters; + + @Before + public void setUp() throws Exception { + // headers + headers = new ContainerRequest(new URI(BASEURI), new URI(REQUESTURI), HttpMethod.GET, null, + new MapPropertiesDelegate()); + // uri info + queryParameters = new MultivaluedHashMap(); + + + providers = new ConcurrentHashMap, IResourceProvider>(); + provider = createConformanceProvider(providers); + } + + @Test + public void testConformance() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); + Response response = createConformanceProvider(providers).conformance(); + System.out.println(response); + } + + @Test + public void testConformanceUsingOptions() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); + Response response = createConformanceProvider(providers).conformanceUsingOptions(); + System.out.println(response); + } + + @Test + public void testConformanceWithMethods() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsMockPatientRestProviderDstu2_1.class, new TestJaxRsMockPatientRestProviderDstu2_1()); + Response response = createConformanceProvider(providers).conformance(); + assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); + assertTrue(response.getEntity().toString().contains("\"type\": \"Patient\"")); + assertTrue(response.getEntity().toString().contains("\"someCustomOperation")); + System.out.println(response); + System.out.println(response.getEntity()); + } + + @Test + public void testConformanceInXml() throws Exception { + queryParameters.put(Constants.PARAM_FORMAT, Arrays.asList(Constants.CT_XML)); + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsMockPatientRestProviderDstu2_1.class, new TestJaxRsMockPatientRestProviderDstu2_1()); + Response response = createConformanceProvider(providers).conformance(); + assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); + System.out.println(response.getEntity()); + assertTrue(response.getEntity().toString().contains(" ")); + assertTrue(response.getEntity().toString().contains("\"someCustomOperation")); + System.out.println(response.getEntity()); + } + + private AbstractJaxRsConformanceProvider createConformanceProvider(final ConcurrentHashMap, IResourceProvider> providers) + throws Exception { + AbstractJaxRsConformanceProvider result = new AbstractJaxRsConformanceProvider(FhirContext.forDstu2_1(), null, null, null) { + @Override + protected ConcurrentHashMap, IResourceProvider> getProviders() { + return providers; + } + }; + // mocks + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getQueryParameters()).thenReturn(queryParameters); + when(uriInfo.getBaseUri()).thenReturn(new URI(BASEURI)); + when(uriInfo.getRequestUri()).thenReturn(new URI(BASEURI + "/foo")); + result.setUriInfo(uriInfo); + result.setHeaders(headers); + result.setUpPostConstruct(); + return result; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu2_1.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu2_1.java new file mode 100644 index 00000000000..08aab47036e --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu2_1.java @@ -0,0 +1,128 @@ +package ca.uhn.fhir.jaxrs.server.test; + +import java.util.List; + +import javax.ejb.Stateless; +import javax.interceptor.Interceptors; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.hl7.fhir.dstu2016may.model.*; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.mockito.Mockito; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.IPagingProvider; + +/** + * A test server delegating each call to a mock + */ +@Path(TestJaxRsMockPatientRestProviderDstu2_1.PATH) +@Stateless +@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML, Constants.CT_FHIR_JSON_NEW, Constants.CT_FHIR_XML_NEW }) +@Interceptors(JaxRsExceptionInterceptor.class) +public class TestJaxRsMockPatientRestProviderDstu2_1 extends AbstractJaxRsResourceProvider { + + static final String PATH = "/Patient"; + + public static final TestJaxRsMockPatientRestProviderDstu2_1 mock = Mockito.mock(TestJaxRsMockPatientRestProviderDstu2_1.class); + + public static final FifoMemoryPagingProvider PAGING_PROVIDER; + + static + { + PAGING_PROVIDER = new FifoMemoryPagingProvider(10); + PAGING_PROVIDER.setDefaultPageSize(10); + PAGING_PROVIDER.setMaximumPageSize(100); + } + + /** + * Constructor + */ + public TestJaxRsMockPatientRestProviderDstu2_1() { + super(FhirContext.forDstu2_1()); + } + + @Search + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name, @RequiredParam(name=Patient.SP_ADDRESS) StringAndListParam theAddressParts) { + return mock.search(name, theAddressParts); + } + + @Update + public MethodOutcome update(@IdParam final IdType theId, @ResourceParam final Patient patient,@ConditionalUrlParam final String theConditional) throws Exception { + return mock.update(theId, patient, theConditional); + } + + @Read + public Patient find(@IdParam final IdType theId) { + return mock.find(theId); + } + + @Read(version = true) + public Patient findHistory(@IdParam final IdType theId) { + return mock.findHistory(theId); + } + + @Create + public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) + throws Exception { + return mock.create(patient, theConditional); + } + + @Delete + public MethodOutcome delete(@IdParam final IdType theId, @ConditionalUrlParam final String theConditional) { + return mock.delete(theId, theConditional); + } + + @Search(compartmentName = "Condition") + public List searchCompartment(@IdParam IdType thePatientId) { + return mock.searchCompartment(thePatientId); + } + + @GET + @Path("/{id}/$someCustomOperation") + public Response someCustomOperationUsingGet(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.GET, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); + } + + @POST + @Path("/{id}/$someCustomOperation") + public Response someCustomOperationUsingPost(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.POST, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); + } + + @Operation(name = "someCustomOperation", idempotent = true, returnParameters = { + @OperationParam(name = "return", type = StringType.class) }) + public Parameters someCustomOperation(@IdParam IdType myId, @OperationParam(name = "dummy") StringType dummyInput) { + return mock.someCustomOperation(myId, dummyInput); + } + + @Validate() + public MethodOutcome validate(@ResourceParam final Patient resource) { + MethodOutcome mO = new MethodOutcome(); + mO.setOperationOutcome(new OperationOutcome()); + return mO; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Override + public IPagingProvider getPagingProvider() { + return PAGING_PROVIDER; + } + +} diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java index 9f69509733b..a1fdc04ce16 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java @@ -21,12 +21,9 @@ package org.hl7.fhir.instance.conf; */ import static org.apache.commons.lang3.StringUtils.isNotBlank; -import java.io.IOException; -import java.io.InputStream; import java.text.*; import java.util.*; import java.util.Map.Entry; -import java.util.jar.Manifest; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -71,7 +68,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider myOperationBindingToName; private HashMap> myOperationNameToBindings; private String myPublisher = "Not provided"; - private RestulfulServerConfiguration myServerConfiguration; + private RestulfulServerConfiguration myServerConfiguration; public ServerConformanceProvider(RestfulServer theRestfulServer) { this.myServerConfiguration = theRestfulServer.createConfiguration(); @@ -91,10 +88,10 @@ public class ServerConformanceProvider implements IServerConformanceProvider Date: Sun, 22 Oct 2017 18:43:17 -0500 Subject: [PATCH 07/39] Related to issue #764 - adds support for the R4 FHIR context in AbstractJaxRsConformanceProvider --- hapi-fhir-jaxrsserver-base/pom.xml | 5 + .../AbstractJaxRsConformanceProvider.java | 16 ++- ...xRsConformanceProviderDstu2Hl7OrgTest.java | 6 +- ...ctJaxRsConformanceProviderDstu2_1Test.java | 6 +- ...bstractJaxRsConformanceProviderR4Test.java | 108 +++++++++++++++ ...tJaxRsDummyPatientProviderDstu2Hl7Org.java | 15 ++ .../TestJaxRsDummyPatientProviderDstu2_1.java | 15 ++ .../test/TestJaxRsDummyPatientProviderR4.java | 15 ++ .../TestJaxRsMockPatientRestProviderR4.java | 128 ++++++++++++++++++ 9 files changed, 305 insertions(+), 9 deletions(-) create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderR4Test.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderDstu2Hl7Org.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderDstu2_1.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderR4.java diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 3f728fccc8e..76b6a885650 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -66,6 +66,11 @@ hapi-fhir-structures-dstu3 ${project.version} + + ca.uhn.hapi.fhir + hapi-fhir-structures-r4 + ${project.version} + javax.ws.rs diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java index 6f76e3f6959..1b7f328e6e9 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java @@ -66,6 +66,7 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv private RestulfulServerConfiguration serverConfiguration = new RestulfulServerConfiguration(); /** the conformance. It is created once during startup */ + private org.hl7.fhir.r4.model.CapabilityStatement myR4CapabilityStatement; private CapabilityStatement myDstu3CapabilityStatement; private org.hl7.fhir.dstu2016may.model.Conformance myDstu2_1Conformance; private ca.uhn.fhir.model.dstu2.resource.Conformance myDstu2Conformance; @@ -126,7 +127,11 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv HardcodedServerAddressStrategy hardcodedServerAddressStrategy = new HardcodedServerAddressStrategy(); hardcodedServerAddressStrategy.setValue(getBaseForServer()); serverConfiguration.setServerAddressStrategy(hardcodedServerAddressStrategy); - if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { + if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.R4)) { + org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider serverCapabilityStatementProvider = new org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider(serverConfiguration); + serverCapabilityStatementProvider.initializeOperations(); + myR4CapabilityStatement = serverCapabilityStatementProvider.getServerConformance(null); + } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { ServerCapabilityStatementProvider serverCapabilityStatementProvider = new ServerCapabilityStatementProvider(serverConfiguration); serverCapabilityStatementProvider.initializeOperations(); myDstu3CapabilityStatement = serverCapabilityStatementProvider.getServerConformance(null); @@ -177,7 +182,10 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv response.addHeader(Constants.HEADER_CORS_ALLOW_ORIGIN, "*"); IBaseResource conformance = null; - if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { + if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.R4)) { + conformance = myR4CapabilityStatement; +// return (Response) response.returnResponse(ParseAction.create(myDstu3CapabilityStatement), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName()); + } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { conformance = myDstu3CapabilityStatement; // return (Response) response.returnResponse(ParseAction.create(myDstu3CapabilityStatement), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName()); } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2_1)) { @@ -271,7 +279,9 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv @SuppressWarnings("unchecked") @Override public Class getResourceType() { - if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { + if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.R4)) { + return Class.class.cast(org.hl7.fhir.r4.model.CapabilityStatement.class); + } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { return Class.class.cast(CapabilityStatement.class); } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2_1)) { return Class.class.cast(org.hl7.fhir.dstu2016may.model.Conformance.class); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2Hl7OrgTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2Hl7OrgTest.java index 9b3a8ca6fda..eac40d8bce2 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2Hl7OrgTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2Hl7OrgTest.java @@ -12,13 +12,13 @@ import java.util.concurrent.ConcurrentHashMap; import javax.ws.rs.HttpMethod; import javax.ws.rs.core.*; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProviderDstu2Hl7Org; import org.glassfish.jersey.internal.MapPropertiesDelegate; import org.glassfish.jersey.server.ContainerRequest; import org.junit.Before; import org.junit.Test; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider; import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProviderDstu2Hl7Org; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.IResourceProvider; @@ -48,7 +48,7 @@ public class AbstractJaxRsConformanceProviderDstu2Hl7OrgTest { @Test public void testConformance() throws Exception { providers.put(AbstractJaxRsConformanceProvider.class, provider); - providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); + providers.put(TestJaxRsDummyPatientProviderDstu2Hl7Org.class, new TestJaxRsDummyPatientProviderDstu2Hl7Org()); Response response = createConformanceProvider(providers).conformance(); System.out.println(response); } @@ -56,7 +56,7 @@ public class AbstractJaxRsConformanceProviderDstu2Hl7OrgTest { @Test public void testConformanceUsingOptions() throws Exception { providers.put(AbstractJaxRsConformanceProvider.class, provider); - providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); + providers.put(TestJaxRsDummyPatientProviderDstu2Hl7Org.class, new TestJaxRsDummyPatientProviderDstu2Hl7Org()); Response response = createConformanceProvider(providers).conformanceUsingOptions(); System.out.println(response); } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2_1Test.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2_1Test.java index 60b447e0ab4..115e68d9210 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2_1Test.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderDstu2_1Test.java @@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap; import javax.ws.rs.HttpMethod; import javax.ws.rs.core.*; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProviderDstu2_1; import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProviderDstu2_1; import org.glassfish.jersey.internal.MapPropertiesDelegate; import org.glassfish.jersey.server.ContainerRequest; @@ -19,7 +20,6 @@ import org.junit.Before; import org.junit.Test; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.IResourceProvider; @@ -48,7 +48,7 @@ public class AbstractJaxRsConformanceProviderDstu2_1Test { @Test public void testConformance() throws Exception { providers.put(AbstractJaxRsConformanceProvider.class, provider); - providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); + providers.put(TestJaxRsDummyPatientProviderDstu2_1.class, new TestJaxRsDummyPatientProviderDstu2_1()); Response response = createConformanceProvider(providers).conformance(); System.out.println(response); } @@ -56,7 +56,7 @@ public class AbstractJaxRsConformanceProviderDstu2_1Test { @Test public void testConformanceUsingOptions() throws Exception { providers.put(AbstractJaxRsConformanceProvider.class, provider); - providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); + providers.put(TestJaxRsDummyPatientProviderDstu2_1.class, new TestJaxRsDummyPatientProviderDstu2_1()); Response response = createConformanceProvider(providers).conformanceUsingOptions(); System.out.println(response); } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderR4Test.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderR4Test.java new file mode 100644 index 00000000000..80c86d751d5 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderR4Test.java @@ -0,0 +1,108 @@ +package ca.uhn.fhir.jaxrs.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.*; + +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProviderR4; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProviderR4; +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.server.ContainerRequest; +import org.junit.Before; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.server.IResourceProvider; + +public class AbstractJaxRsConformanceProviderR4Test { + + private static final String BASEURI = "http://basiuri"; + private static final String REQUESTURI = BASEURI + "/metadata"; + AbstractJaxRsConformanceProvider provider; + private ConcurrentHashMap, IResourceProvider> providers; + private ContainerRequest headers; + private MultivaluedHashMap queryParameters; + + @Before + public void setUp() throws Exception { + // headers + headers = new ContainerRequest(new URI(BASEURI), new URI(REQUESTURI), HttpMethod.GET, null, + new MapPropertiesDelegate()); + // uri info + queryParameters = new MultivaluedHashMap(); + + + providers = new ConcurrentHashMap, IResourceProvider>(); + provider = createConformanceProvider(providers); + } + + @Test + public void testConformance() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsDummyPatientProviderR4.class, new TestJaxRsDummyPatientProviderR4()); + Response response = createConformanceProvider(providers).conformance(); + System.out.println(response); + } + + @Test + public void testConformanceUsingOptions() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsDummyPatientProviderR4.class, new TestJaxRsDummyPatientProviderR4()); + Response response = createConformanceProvider(providers).conformanceUsingOptions(); + System.out.println(response); + } + + @Test + public void testConformanceWithMethods() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsMockPatientRestProviderR4.class, new TestJaxRsMockPatientRestProviderR4()); + Response response = createConformanceProvider(providers).conformance(); + assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); + assertTrue(response.getEntity().toString().contains("\"type\": \"Patient\"")); + assertTrue(response.getEntity().toString().contains("\"someCustomOperation")); + System.out.println(response); + System.out.println(response.getEntity()); + } + + @Test + public void testConformanceInXml() throws Exception { + queryParameters.put(Constants.PARAM_FORMAT, Arrays.asList(Constants.CT_XML)); + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsMockPatientRestProviderR4.class, new TestJaxRsMockPatientRestProviderR4()); + Response response = createConformanceProvider(providers).conformance(); + assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); + System.out.println(response.getEntity()); + assertTrue(response.getEntity().toString().contains(" ")); + assertTrue(response.getEntity().toString().contains("\"someCustomOperation")); + System.out.println(response.getEntity()); + } + + private AbstractJaxRsConformanceProvider createConformanceProvider(final ConcurrentHashMap, IResourceProvider> providers) + throws Exception { + AbstractJaxRsConformanceProvider result = new AbstractJaxRsConformanceProvider(FhirContext.forR4(), null, null, null) { + @Override + protected ConcurrentHashMap, IResourceProvider> getProviders() { + return providers; + } + }; + // mocks + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getQueryParameters()).thenReturn(queryParameters); + when(uriInfo.getBaseUri()).thenReturn(new URI(BASEURI)); + when(uriInfo.getRequestUri()).thenReturn(new URI(BASEURI + "/foo")); + result.setUriInfo(uriInfo); + result.setHeaders(headers); + result.setUpPostConstruct(); + return result; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderDstu2Hl7Org.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderDstu2Hl7Org.java new file mode 100644 index 00000000000..c41b8611024 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderDstu2Hl7Org.java @@ -0,0 +1,15 @@ +package ca.uhn.fhir.jaxrs.server.test; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import org.hl7.fhir.instance.model.Patient; + +/** + * A dummy patient provider exposing no methods + */ +public class TestJaxRsDummyPatientProviderDstu2Hl7Org extends AbstractJaxRsResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderDstu2_1.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderDstu2_1.java new file mode 100644 index 00000000000..420aa5cee48 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderDstu2_1.java @@ -0,0 +1,15 @@ +package ca.uhn.fhir.jaxrs.server.test; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import org.hl7.fhir.dstu2016may.model.Patient; + +/** + * A dummy patient provider exposing no methods + */ +public class TestJaxRsDummyPatientProviderDstu2_1 extends AbstractJaxRsResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4.java new file mode 100644 index 00000000000..1c3f91f4c1d --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4.java @@ -0,0 +1,15 @@ +package ca.uhn.fhir.jaxrs.server.test; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import org.hl7.fhir.r4.model.Patient; + +/** + * A dummy patient provider exposing no methods + */ +public class TestJaxRsDummyPatientProviderR4 extends AbstractJaxRsResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderR4.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderR4.java new file mode 100644 index 00000000000..0b34916b778 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderR4.java @@ -0,0 +1,128 @@ +package ca.uhn.fhir.jaxrs.server.test; + +import java.util.List; + +import javax.ejb.Stateless; +import javax.interceptor.Interceptors; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.mockito.Mockito; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.IPagingProvider; + +/** + * A test server delegating each call to a mock + */ +@Path(TestJaxRsMockPatientRestProviderR4.PATH) +@Stateless +@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML, Constants.CT_FHIR_JSON_NEW, Constants.CT_FHIR_XML_NEW }) +@Interceptors(JaxRsExceptionInterceptor.class) +public class TestJaxRsMockPatientRestProviderR4 extends AbstractJaxRsResourceProvider { + + static final String PATH = "/Patient"; + + public static final TestJaxRsMockPatientRestProviderR4 mock = Mockito.mock(TestJaxRsMockPatientRestProviderR4.class); + + public static final FifoMemoryPagingProvider PAGING_PROVIDER; + + static + { + PAGING_PROVIDER = new FifoMemoryPagingProvider(10); + PAGING_PROVIDER.setDefaultPageSize(10); + PAGING_PROVIDER.setMaximumPageSize(100); + } + + /** + * Constructor + */ + public TestJaxRsMockPatientRestProviderR4() { + super(FhirContext.forR4()); + } + + @Search + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name, @RequiredParam(name=Patient.SP_ADDRESS) StringAndListParam theAddressParts) { + return mock.search(name, theAddressParts); + } + + @Update + public MethodOutcome update(@IdParam final IdType theId, @ResourceParam final Patient patient,@ConditionalUrlParam final String theConditional) throws Exception { + return mock.update(theId, patient, theConditional); + } + + @Read + public Patient find(@IdParam final IdType theId) { + return mock.find(theId); + } + + @Read(version = true) + public Patient findHistory(@IdParam final IdType theId) { + return mock.findHistory(theId); + } + + @Create + public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) + throws Exception { + return mock.create(patient, theConditional); + } + + @Delete + public MethodOutcome delete(@IdParam final IdType theId, @ConditionalUrlParam final String theConditional) { + return mock.delete(theId, theConditional); + } + + @Search(compartmentName = "Condition") + public List searchCompartment(@IdParam IdType thePatientId) { + return mock.searchCompartment(thePatientId); + } + + @GET + @Path("/{id}/$someCustomOperation") + public Response someCustomOperationUsingGet(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.GET, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); + } + + @POST + @Path("/{id}/$someCustomOperation") + public Response someCustomOperationUsingPost(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.POST, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); + } + + @Operation(name = "someCustomOperation", idempotent = true, returnParameters = { + @OperationParam(name = "return", type = StringType.class) }) + public Parameters someCustomOperation(@IdParam IdType myId, @OperationParam(name = "dummy") StringType dummyInput) { + return mock.someCustomOperation(myId, dummyInput); + } + + @Validate() + public MethodOutcome validate(@ResourceParam final Patient resource) { + MethodOutcome mO = new MethodOutcome(); + mO.setOperationOutcome(new OperationOutcome()); + return mO; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Override + public IPagingProvider getPagingProvider() { + return PAGING_PROVIDER; + } + +} From d0e747d6ecf29fc1ed08dbb3a47853f22c815844 Mon Sep 17 00:00:00 2001 From: Clayton Bodendein Date: Sun, 22 Oct 2017 22:20:48 -0500 Subject: [PATCH 08/39] Fixed issue #768 in a different way by wrapping the DataFormatException as an InvalidRequestException so that it still gets sent as a 400 response but now DataFormatException stacktraces can now be conditionally sent based on the ExceptionHandlingInterceptor#setReturnStackTracesForExceptionTypes(Class...) configuration --- .../exceptions/InvalidRequestException.java | 4 ++++ .../fhir/jaxrs/server/AbstractJaxRsProvider.java | 15 --------------- .../interceptor/ExceptionHandlingInterceptor.java | 7 ++++++- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/InvalidRequestException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/InvalidRequestException.java index 31a51f292ab..e56d3abd620 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/InvalidRequestException.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/InvalidRequestException.java @@ -47,6 +47,10 @@ public class InvalidRequestException extends BaseServerResponseException { public InvalidRequestException(String theMessage) { super(STATUS_CODE, theMessage); } + + public InvalidRequestException(Throwable theCause) { + super(STATUS_CODE, theCause); + } /** * Constructor diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index 632a42935c8..a0194aa1451 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -26,8 +26,6 @@ import java.util.Map.Entry; import javax.ws.rs.core.*; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.api.AddProfileTagEnum; @@ -35,12 +33,9 @@ import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder; -import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.server.*; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.util.OperationOutcomeUtil; /** * This is the abstract superclass for all jaxrs providers. It contains some defaults implementing @@ -79,13 +74,6 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { CTX = ctx; } - private IBaseOperationOutcome createOutcome(final DataFormatException theException) { - final IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getFhirContext()); - final String detailsValue = theException.getMessage() + (this.withStackTrace() ? "\n\n" + ExceptionUtils.getStackTrace(theException) : ""); - OperationOutcomeUtil.addIssue(getFhirContext(), oo, ERROR, detailsValue, null, PROCESSING); - return oo; - } - /** * DEFAULT = AddProfileTagEnum.NEVER */ @@ -241,9 +229,6 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { throws IOException { if (theException instanceof JaxRsResponseException) { return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, (JaxRsResponseException) theException); - } else if (theException instanceof DataFormatException) { - return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, new JaxRsResponseException( - new InvalidRequestException(theException.getMessage(), createOutcome((DataFormatException) theException)))); } else { return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, new JaxRsExceptionInterceptor().convertException(this, theException)); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java index 55d61dc93e7..da2bba0e053 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java @@ -32,6 +32,8 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.apache.commons.lang3.exception.ExceptionUtils; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; @@ -99,7 +101,10 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter { @Override public BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException { BaseServerResponseException retVal; - if (!(theException instanceof BaseServerResponseException)) { + if (theException instanceof DataFormatException) { + // Wrapping the DataFormatException as an InvalidRequestException so that it gets sent back to the client as a 400 response. + retVal = new InvalidRequestException(theException); + } else if (!(theException instanceof BaseServerResponseException)) { retVal = new InternalErrorException(theException); } else { retVal = (BaseServerResponseException) theException; From 8d1164fdc08f1c11dd76e01c25b55bc34ffdd80e Mon Sep 17 00:00:00 2001 From: Clayton Bodendein Date: Mon, 23 Oct 2017 21:34:21 -0500 Subject: [PATCH 09/39] Fixed the Conformance providers in the hapi-fhir-jaxrsserver-example module to pass the server description, server name, and server version in the correct order --- .../uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java | 2 +- .../jaxrs/server/example/JaxRsConformanceProviderDstu3.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java index d70cf8795c7..e54f27c2e56 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java @@ -32,7 +32,7 @@ public class JaxRsConformanceProvider extends AbstractJaxRsConformanceProvider { * Standard Constructor */ public JaxRsConformanceProvider() { - super(SERVER_VERSION, SERVER_DESCRIPTION, SERVER_NAME); + super(SERVER_DESCRIPTION, SERVER_NAME, SERVER_VERSION); } @Override diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProviderDstu3.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProviderDstu3.java index 80ebaebd9cd..462ad5203a7 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProviderDstu3.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProviderDstu3.java @@ -33,7 +33,7 @@ public class JaxRsConformanceProviderDstu3 extends AbstractJaxRsConformanceProvi * Standard Constructor */ public JaxRsConformanceProviderDstu3() { - super(FhirContext.forDstu3(), SERVER_VERSION, SERVER_DESCRIPTION, SERVER_NAME); + super(FhirContext.forDstu3(), SERVER_DESCRIPTION, SERVER_NAME, SERVER_VERSION); } @Override From 129340d525acf2fa8fcfd40d50d81cdce2f12fb4 Mon Sep 17 00:00:00 2001 From: Clayton Bodendein Date: Mon, 23 Oct 2017 21:43:23 -0500 Subject: [PATCH 10/39] Fixed parameter order in the example JaxRsConformanceProvider --- examples/src/main/java/example/JaxRsConformanceProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/main/java/example/JaxRsConformanceProvider.java b/examples/src/main/java/example/JaxRsConformanceProvider.java index 0accfd92f60..1f1549f07f7 100644 --- a/examples/src/main/java/example/JaxRsConformanceProvider.java +++ b/examples/src/main/java/example/JaxRsConformanceProvider.java @@ -27,7 +27,7 @@ public class JaxRsConformanceProvider extends AbstractJaxRsConformanceProvider { private JaxRsPatientRestProvider provider; public JaxRsConformanceProvider() { - super("My Server Version", "My Server Description", "My Server Name"); + super("My Server Description", "My Server Name", "My Server Version"); } @Override From d7f13432dfc04b18e06fe93ab695fdb396baea3c Mon Sep 17 00:00:00 2001 From: Clayton Bodendein Date: Mon, 30 Oct 2017 12:48:23 -0500 Subject: [PATCH 11/39] Related to issue #764, changed the private conformanceDate method in DSTU2_HL7ORG's ServerConformanceProvider to be consistent with the ServerConformanceProvider in other FHIR contexts --- .../instance/conf/ServerConformanceProvider.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java index a1fdc04ce16..a410287cfda 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java @@ -29,6 +29,7 @@ import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.parser.DataFormatException; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.*; import org.hl7.fhir.instance.model.Conformance.*; @@ -165,7 +166,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider Date: Mon, 30 Oct 2017 16:39:30 -0400 Subject: [PATCH 12/39] Removed "hapi-fhir" from 4x Learn More links --- src/site/resources/svg/hapi_usage_patterns.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/site/resources/svg/hapi_usage_patterns.svg b/src/site/resources/svg/hapi_usage_patterns.svg index cf328c2c567..730b07d8e95 100644 --- a/src/site/resources/svg/hapi_usage_patterns.svg +++ b/src/site/resources/svg/hapi_usage_patterns.svg @@ -163,7 +163,7 @@ Use the HAPI FHIR client in an application to fetch from or store resources to an external server.
- Learn Mode + Learn Mode [Not supported by viewer] @@ -266,7 +266,7 @@ Use the HAPI FHIR server in an application to allow external applications to access or modify your application's data.
- Learn More + Learn More
- Learn More + Learn More [Not supported by viewer] @@ -572,7 +572,7 @@ Use the HAPI FHIR parser and encoder to convert between FHIR and your application's data model.
- Learn More + Learn More [Not supported by viewer] From 4042a3a3532e83bebf7e61737e3fdf617b8e4734 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 22 Nov 2017 18:17:35 -0500 Subject: [PATCH 13/39] Prevent accidental deletion of deeply nested resources in JPA server --- .../java/ca/uhn/fhir/parser/BaseParser.java | 2 +- .../java/ca/uhn/fhir/parser/JsonParser.java | 2 +- .../provider/r4/ResourceProviderR4Test.java | 50 +++++++++++++++++++ .../ca/uhn/fhir/parser/JsonParserR4Test.java | 46 +++++++++++++++++ .../ca/uhn/fhir/parser/XmlParserR4Test.java | 30 +++++++++++ src/changes/changes.xml | 4 ++ 6 files changed, 132 insertions(+), 2 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 9dfcdb7c7da..da8f83f9957 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -1115,7 +1115,7 @@ public abstract class BaseParser implements IParser { } } - return true; + return false; } public BaseRuntimeChildDefinition getDef() { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index f759cefa6cd..de7286a98dd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -1434,7 +1434,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } BaseRuntimeElementDefinition childDef = extDef.getChildElementDefinitionByDatatype(value.getClass()); if (childDef == null) { - throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + value.getClass().getCanonicalName()); + throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); } encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, false, myParent, false); managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index 5c6981cccb5..0f0a5920ff2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -4301,6 +4301,56 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } } + @Test + public void testParseAndEncodeExtensionWithValueWithExtension() throws IOException { + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + IdType id; + try { + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id = new IdType(newIdString); + } finally { + response.close(); + } + + HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart() + "?_pretty=true"); + response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + assertThat(resp, containsString("Underweight")); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + + } + + + @Test public void testValueSetExpandOperation() throws IOException { CodeSystem cs = myFhirCtx.newXmlParser().parseResource(CodeSystem.class, new InputStreamReader(ResourceProviderR4Test.class.getResourceAsStream("/extensional-case-3-cs.xml"))); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java index 93fe167ff77..64f379f9160 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.parser; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.util.TestUtil; +import com.google.common.collect.Sets; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Patient; import org.junit.AfterClass; @@ -110,6 +111,51 @@ public class JsonParserR4Test { return b; } + + @Test + public void testParseAndEncodeExtensionWithValueWithExtension() { + String input = "{\n" + + " \"resourceType\": \"Patient\",\n" + + " \"extension\": [\n" + + " {\n" + + " \"url\": \"https://purl.org/elab/fhir/network/StructureDefinition/1/BirthWeight\",\n" + + " \"_valueDecimal\": {\n" + + " \"extension\": [\n" + + " {\n" + + " \"url\": \"http://www.hl7.org/fhir/extension-data-absent-reason.html\",\n" + + " \"valueCoding\": {\n" + + " \"system\": \"http://hl7.org/fhir/ValueSet/birthweight\",\n" + + " \"code\": \"Underweight\",\n" + + " \"userSelected\": false\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"identifier\": [\n" + + " {\n" + + " \"system\": \"https://purl.org/elab/fhir/network/StructureDefinition/1/EuroPrevallStudySubjects\",\n" + + " \"value\": \"1\"\n" + + " }\n" + + " ],\n" + + " \"gender\": \"female\"\n" + + "}"; + + IParser jsonParser = ourCtx.newJsonParser(); + IParser xmlParser = ourCtx.newXmlParser(); + jsonParser.setDontEncodeElements(Sets.newHashSet("id", "meta")); + xmlParser.setDontEncodeElements(Sets.newHashSet("id", "meta")); + + Patient parsed = jsonParser.parseResource(Patient.class, input); + + ourLog.info(jsonParser.setPrettyPrint(true).encodeResourceToString(parsed)); + assertThat(xmlParser.encodeResourceToString(parsed), containsString("Underweight")); + assertThat(jsonParser.encodeResourceToString(parsed), containsString("Underweight")); + + } + + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java index 2d16f0e9c56..5be2e8e7977 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java @@ -110,6 +110,36 @@ public class XmlParserR4Test { return b; } + @Test + public void testParseAndEncodeExtensionWithValueWithExtension() { + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + Patient parsed = ourCtx.newXmlParser().parseResource(Patient.class, input); + + ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed)); + assertThat(ourCtx.newXmlParser().encodeResourceToString(parsed), containsString("Underweight")); + assertThat(ourCtx.newJsonParser().encodeResourceToString(parsed), containsString("Underweight")); + + } + + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 21cee568c1f..51dc07483f1 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -212,6 +212,10 @@ client requests. Thanks to Clayton Bodendein for the pull request! + + An issue was fixed in JPA server where extensions on primitives which + are nestedt several layers deep are lost when resources are retrieved + From a4cbde82693bd26117daeaa9894a80fa4de97729 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 18:45:54 -0500 Subject: [PATCH 14/39] Prevent a spurious test failure --- .../ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index 0f0a5920ff2..d3b04381029 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -3219,7 +3219,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { // If this fails under load, try increasing the throttle above assertEquals(null, found.getTotalElement().getValue()); assertEquals(1, found.getEntry().size()); - assertThat(sw.getMillis(), lessThan(1000L)); + assertThat(sw.getMillis(), lessThan(1500L)); } @Test From 059265a0413c4ff8dcbeac66d5ca456404e59a1d Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 19:15:27 -0500 Subject: [PATCH 15/39] Test cleanup --- .../java/ca/uhn/fhir/util/BinaryUtil.java | 20 ++++ .../ca/uhn/fhir/parser/XmlParserR4Test.java | 92 ++++++++++--------- 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java index b6c4411c8f3..cfa4323d718 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.util; +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2017 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.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java index 5be2e8e7977..9be7c175f0c 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.parser; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.util.TestUtil; +import com.google.common.collect.Sets; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Patient; import org.junit.AfterClass; @@ -20,6 +21,19 @@ public class XmlParserR4Test { private static final Logger ourLog = LoggerFactory.getLogger(XmlParserR4Test.class); private static FhirContext ourCtx = FhirContext.forR4(); + private Bundle createBundleWithPatient() { + Bundle b = new Bundle(); + b.setId("BUNDLEID"); + b.getMeta().addProfile("http://FOO"); + + Patient p = new Patient(); + p.setId("PATIENTID"); + p.getMeta().addProfile("http://BAR"); + p.addName().addGiven("GIVEN"); + b.addEntry().setResource(p); + return b; + } + @Test public void testExcludeNothing() { IParser parser = ourCtx.newXmlParser().setPrettyPrint(true); @@ -45,6 +59,32 @@ public class XmlParserR4Test { assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString()); } + @Test + public void testExcludeRootStuff() { + IParser parser = ourCtx.newXmlParser().setPrettyPrint(true); + Set excludes = new HashSet<>(); + excludes.add("id"); + excludes.add("meta"); + parser.setDontEncodeElements(excludes); + + Bundle b = createBundleWithPatient(); + + String encoded = parser.encodeResourceToString(b); + ourLog.info(encoded); + + assertThat(encoded, not(containsString("BUNDLEID"))); + assertThat(encoded, not(containsString("http://FOO"))); + assertThat(encoded, (containsString("PATIENTID"))); + assertThat(encoded, (containsString("http://BAR"))); + assertThat(encoded, containsString("GIVEN")); + + b = parser.parseResource(Bundle.class, encoded); + + assertNotEquals("BUNDLEID", b.getIdElement().getIdPart()); + assertEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId()); + assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString()); + } + @Test public void testExcludeStarDotStuff() { IParser parser = ourCtx.newXmlParser().setPrettyPrint(true); @@ -71,45 +111,6 @@ public class XmlParserR4Test { assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString()); } - @Test - public void testExcludeRootStuff() { - IParser parser = ourCtx.newXmlParser().setPrettyPrint(true); - Set excludes = new HashSet<>(); - excludes.add("id"); - excludes.add("meta"); - parser.setDontEncodeElements(excludes); - - Bundle b = createBundleWithPatient(); - - String encoded = parser.encodeResourceToString(b); - ourLog.info(encoded); - - assertThat(encoded, not(containsString("BUNDLEID"))); - assertThat(encoded, not(containsString("http://FOO"))); - assertThat(encoded, (containsString("PATIENTID"))); - assertThat(encoded, (containsString("http://BAR"))); - assertThat(encoded, containsString("GIVEN")); - - b = parser.parseResource(Bundle.class, encoded); - - assertNotEquals("BUNDLEID", b.getIdElement().getIdPart()); - assertEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId()); - assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString()); - } - - private Bundle createBundleWithPatient() { - Bundle b = new Bundle(); - b.setId("BUNDLEID"); - b.getMeta().addProfile("http://FOO"); - - Patient p = new Patient(); - p.setId("PATIENTID"); - p.getMeta().addProfile("http://BAR"); - p.addName().addGiven("GIVEN"); - b.addEntry().setResource(p); - return b; - } - @Test public void testParseAndEncodeExtensionWithValueWithExtension() { String input = "\n" + @@ -131,11 +132,16 @@ public class XmlParserR4Test { " \n" + ""; - Patient parsed = ourCtx.newXmlParser().parseResource(Patient.class, input); + IParser xmlParser = ourCtx.newXmlParser(); + IParser jsonParser = ourCtx.newJsonParser(); + jsonParser.setDontEncodeElements(Sets.newHashSet("id", "meta")); + xmlParser.setDontEncodeElements(Sets.newHashSet("id", "meta")); - ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed)); - assertThat(ourCtx.newXmlParser().encodeResourceToString(parsed), containsString("Underweight")); - assertThat(ourCtx.newJsonParser().encodeResourceToString(parsed), containsString("Underweight")); + Patient parsed = xmlParser.parseResource(Patient.class, input); + + ourLog.info(jsonParser.setPrettyPrint(true).encodeResourceToString(parsed)); + assertThat(xmlParser.encodeResourceToString(parsed), containsString("Underweight")); + assertThat(jsonParser.encodeResourceToString(parsed), containsString("Underweight")); } From 5a8e88200bec9b3125822640a4e68f564e224452 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 19:18:49 -0500 Subject: [PATCH 16/39] Credit for #756 --- src/changes/changes.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 51dc07483f1..a00fb9d2921 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -216,6 +216,11 @@ An issue was fixed in JPA server where extensions on primitives which are nestedt several layers deep are lost when resources are retrieved + + Conditional deletes in JPA server were incorrectly denied by AuthorizationInterceptor + if the delete was permitted via a compartment rule. Thanks to Alvin Leonard for the + pull request! + From be76b90e7a474be4d641da964be7d6c23720a1ef Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 19:22:32 -0500 Subject: [PATCH 17/39] Test formatting only --- ...nInterceptorResourceProviderDstu3Test.java | 263 +++++++++--------- 1 file changed, 132 insertions(+), 131 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/AuthorizationInterceptorResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/AuthorizationInterceptorResourceProviderDstu3Test.java index 48c68698726..7f6afcbea30 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/AuthorizationInterceptorResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/AuthorizationInterceptorResourceProviderDstu3Test.java @@ -1,39 +1,39 @@ package ca.uhn.fhir.jpa.provider.dstu3; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; - -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.*; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +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.util.TestUtil; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.AfterClass; +import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.apache.http.client.methods.*; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.AfterClass; -import org.junit.Test; - -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.auth.*; -import ca.uhn.fhir.util.TestUtil; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.*; public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @Override public void before() throws Exception { super.before(); @@ -41,96 +41,66 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou unregisterInterceptors(); } - - private void unregisterInterceptors() { - for (IServerInterceptor next : new ArrayList(ourRestServer.getInterceptors())) { - if (next instanceof AuthorizationInterceptor) { - ourRestServer.unregisterInterceptor(next); - } - } - } - /** - * See #503 + * See #667 */ @Test - public void testDeleteIsBlocked() { - + public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException { + Patient pt1 = new Patient(); + pt1.setActive(true); + final IIdType pid1 = ourClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless(); + + Patient pt2 = new Patient(); + pt2.setActive(false); + final IIdType pid2 = ourClient.create().resource(pt2).execute().getId().toUnqualifiedVersionless(); + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @Override public List buildRuleList(RequestDetails theRequestDetails) { return new RuleBuilder() - .deny().delete().allResources().withAnyId().andThen() - .allowAll() - .build(); + .allow().write().allResources().inCompartment("Patient", pid1).andThen() + .build(); } }); - - Patient patient = new Patient(); - patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); - patient.addName().setFamily("Tester").addGiven("Raghad"); - IIdType id = ourClient.create().resource(patient).execute().getId(); - try { - ourClient.delete().resourceById(id.toUnqualifiedVersionless()).execute(); - fail(); - } catch (ForbiddenOperationException e) { - // good - } - - patient = ourClient.read().resource(Patient.class).withId(id.toUnqualifiedVersionless()).execute(); - assertEquals(id.getValue(), patient.getId()); - } - + Observation obs = new Observation(); + obs.setStatus(ObservationStatus.FINAL); + obs.setSubject(new Reference(pid1)); + IIdType oid = ourClient.create().resource(obs).execute().getId().toUnqualified(); - /** - * See #503 #751 - */ - @Test - public void testDeleteIsAllowedForCompartment() { - - Patient patient = new Patient(); - patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); - patient.addName().setFamily("Tester").addGiven("Raghad"); - final IIdType id = ourClient.create().resource(patient).execute().getId(); - - Observation obsInCompartment = new Observation(); - obsInCompartment.setStatus(ObservationStatus.FINAL); - obsInCompartment.getSubject().setReferenceElement(id.toUnqualifiedVersionless()); - IIdType obsInCompartmentId = ourClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless(); - - // create a 2nd observation to be deleted by url Observation?patient=id - ourClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless(); - - Observation obsNotInCompartment = new Observation(); - obsNotInCompartment.setStatus(ObservationStatus.FINAL); - IIdType obsNotInCompartmentId = ourClient.create().resource(obsNotInCompartment).execute().getId().toUnqualifiedVersionless(); - + + unregisterInterceptors(); ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @Override public List buildRuleList(RequestDetails theRequestDetails) { return new RuleBuilder() - .allow().delete().resourcesOfType(Observation.class).inCompartment("Patient", id).andThen() - .deny().delete().allResources().withAnyId().andThen() - .allowAll() - .build(); + .allow().write().allResources().inCompartment("Patient", pid2).andThen() + .build(); } }); - - ourClient.delete().resourceById(obsInCompartmentId.toUnqualifiedVersionless()).execute(); - ourClient.delete().resourceConditionalByUrl("Observation?patient=" + id.toUnqualifiedVersionless()).execute(); + + /* + * Try to update to a new patient. The user has access to write to things in + * pid2's compartment, so this would normally be ok, but in this case they are overwriting + * a resource that is already in pid1's compartment, which shouldn't be allowed. + */ + obs = new Observation(); + obs.setId(oid); + obs.setStatus(ObservationStatus.FINAL); + obs.setSubject(new Reference(pid2)); try { - ourClient.delete().resourceById(obsNotInCompartmentId.toUnqualifiedVersionless()).execute(); + ourClient.update().resource(obs).execute(); fail(); } catch (ForbiddenOperationException e) { // good } + } @Test public void testCreateConditional() { - + Patient patient = new Patient(); patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); patient.addName().setFamily("Tester").addGiven("Raghad"); @@ -139,15 +109,13 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @Override public List buildRuleList(RequestDetails theRequestDetails) { - //@formatter:off return new RuleBuilder() .allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/" + output1.getId().getIdPart())).andThen() .allow().updateConditional().allResources() .build(); - //@formatter:on } }); - + patient = new Patient(); patient.setId(output1.getId().toUnqualifiedVersionless()); patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); @@ -155,7 +123,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou MethodOutcome output2 = ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute(); assertEquals(output1.getId().getIdPart(), output2.getId().getIdPart()); - + patient = new Patient(); patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); patient.addName().setFamily("Tester").addGiven("Raghad"); @@ -180,62 +148,82 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou } /** - * See #667 + * See #503 #751 */ @Test - public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException { - Patient pt1 = new Patient(); - pt1.setActive(true); - final IIdType pid1 = ourClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless(); + public void testDeleteIsAllowedForCompartment() { - Patient pt2 = new Patient(); - pt2.setActive(false); - final IIdType pid2 = ourClient.create().resource(pt2).execute().getId().toUnqualifiedVersionless(); + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + final IIdType id = ourClient.create().resource(patient).execute().getId(); + + Observation obsInCompartment = new Observation(); + obsInCompartment.setStatus(ObservationStatus.FINAL); + obsInCompartment.getSubject().setReferenceElement(id.toUnqualifiedVersionless()); + IIdType obsInCompartmentId = ourClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless(); + + // create a 2nd observation to be deleted by url Observation?patient=id + ourClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless(); + + Observation obsNotInCompartment = new Observation(); + obsNotInCompartment.setStatus(ObservationStatus.FINAL); + IIdType obsNotInCompartmentId = ourClient.create().resource(obsNotInCompartment).execute().getId().toUnqualifiedVersionless(); ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @Override public List buildRuleList(RequestDetails theRequestDetails) { return new RuleBuilder() - .allow().write().allResources().inCompartment("Patient", pid1).andThen() + .allow().delete().resourcesOfType(Observation.class).inCompartment("Patient", id).andThen() + .deny().delete().allResources().withAnyId().andThen() + .allowAll() .build(); } }); - - Observation obs = new Observation(); - obs.setStatus(ObservationStatus.FINAL); - obs.setSubject(new Reference(pid1)); - IIdType oid = ourClient.create().resource(obs).execute().getId().toUnqualified(); - - unregisterInterceptors(); - ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - return new RuleBuilder() - .allow().write().allResources().inCompartment("Patient", pid2).andThen() - .build(); - } - }); - - /* - * Try to update to a new patient. The user has access to write to things in - * pid2's compartment, so this would normally be ok, but in this case they are overwriting - * a resource that is already in pid1's compartment, which shouldn't be allowed. - */ - obs = new Observation(); - obs.setId(oid); - obs.setStatus(ObservationStatus.FINAL); - obs.setSubject(new Reference(pid2)); - + ourClient.delete().resourceById(obsInCompartmentId.toUnqualifiedVersionless()).execute(); + ourClient.delete().resourceConditionalByUrl("Observation?patient=" + id.toUnqualifiedVersionless()).execute(); + try { - ourClient.update().resource(obs).execute(); + ourClient.delete().resourceById(obsNotInCompartmentId.toUnqualifiedVersionless()).execute(); fail(); } catch (ForbiddenOperationException e) { // good } - } - + + /** + * See #503 + */ + @Test + public void testDeleteIsBlocked() { + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .deny().delete().allResources().withAnyId().andThen() + .allowAll() + .build(); + } + }); + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + IIdType id = ourClient.create().resource(patient).execute().getId(); + + try { + ourClient.delete().resourceById(id.toUnqualifiedVersionless()).execute(); + fail(); + } catch (ForbiddenOperationException e) { + // good + } + + patient = ourClient.read().resource(Patient.class).withId(id.toUnqualifiedVersionless()).execute(); + assertEquals(id.getValue(), patient.getId()); + } + @Test public void testDeleteResourceConditional() throws IOException { String methodName = "testDeleteResourceConditional"; @@ -284,7 +272,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou //@formatter:on } }); - + HttpDelete delete = new HttpDelete(ourServerBase + "/Patient?name=" + methodName); response = ourHttpClient.execute(delete); try { @@ -303,4 +291,17 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou } + private void unregisterInterceptors() { + for (IServerInterceptor next : new ArrayList(ourRestServer.getInterceptors())) { + if (next instanceof AuthorizationInterceptor) { + ourRestServer.unregisterInterceptor(next); + } + } + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + } From 98b254913524c272856a57203ba0e664ea530042 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 19:45:18 -0500 Subject: [PATCH 18/39] Formatting --- ...tionInterceptorResourceProviderR4Test.java | 320 +++++++++--------- 1 file changed, 162 insertions(+), 158 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java index 2ac8b37a4f1..7c43f896e51 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java @@ -1,41 +1,40 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; - -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.*; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +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.util.TestUtil; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.junit.AfterClass; +import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; -import org.apache.http.client.methods.*; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Observation.ObservationStatus; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.AfterClass; -import org.junit.Test; - -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.auth.*; -import ca.uhn.fhir.util.TestUtil; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.*; public class AuthorizationInterceptorResourceProviderR4Test extends BaseResourceProviderR4Test { - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @Override public void before() throws Exception { super.before(); @@ -43,45 +42,112 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource unregisterInterceptors(); } - - private void unregisterInterceptors() { - for (IServerInterceptor next : new ArrayList(ourRestServer.getInterceptors())) { - if (next instanceof AuthorizationInterceptor) { - ourRestServer.unregisterInterceptor(next); - } - } - } - /** - * See #503 + * See #667 */ @Test - public void testDeleteIsBlocked() { - + public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException { + Patient pt1 = new Patient(); + pt1.setActive(true); + final IIdType pid1 = myClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless(); + + Patient pt2 = new Patient(); + pt2.setActive(false); + final IIdType pid2 = myClient.create().resource(pt2).execute().getId().toUnqualifiedVersionless(); + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @Override public List buildRuleList(RequestDetails theRequestDetails) { return new RuleBuilder() - .deny().delete().allResources().withAnyId().andThen() - .allowAll() - .build(); + .allow().write().allResources().inCompartment("Patient", pid1).andThen() + .build(); } }); - - Patient patient = new Patient(); - patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); - patient.addName().setFamily("Tester").addGiven("Raghad"); - IIdType id = myClient.create().resource(patient).execute().getId(); + + Observation obs = new Observation(); + obs.setStatus(ObservationStatus.FINAL); + obs.setSubject(new Reference(pid1)); + IIdType oid = myClient.create().resource(obs).execute().getId().toUnqualified(); + + + unregisterInterceptors(); + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow().write().allResources().inCompartment("Patient", pid2).andThen() + .build(); + } + }); + + /* + * Try to update to a new patient. The user has access to write to things in + * pid2's compartment, so this would normally be ok, but in this case they are overwriting + * a resource that is already in pid1's compartment, which shouldn't be allowed. + */ + obs = new Observation(); + obs.setId(oid); + obs.setStatus(ObservationStatus.FINAL); + obs.setSubject(new Reference(pid2)); try { - myClient.delete().resourceById(id.toUnqualifiedVersionless()).execute(); + myClient.update().resource(obs).execute(); fail(); } catch (ForbiddenOperationException e) { // good } - - patient = myClient.read().resource(Patient.class).withId(id.toUnqualifiedVersionless()).execute(); - assertEquals(id.getValue(), patient.getId()); + + } + + @Test + public void testCreateConditional() { + + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + final MethodOutcome output1 = myClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute(); + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + //@formatter:off + return new RuleBuilder() + .allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/" + output1.getId().getIdPart())).andThen() + .allow().updateConditional().allResources() + .build(); + //@formatter:on + } + }); + + patient = new Patient(); + patient.setId(output1.getId().toUnqualifiedVersionless()); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + MethodOutcome output2 = myClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute(); + + assertEquals(output1.getId().getIdPart(), output2.getId().getIdPart()); + + patient = new Patient(); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + try { + myClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|101").execute(); + fail(); + } catch (ForbiddenOperationException e) { + assertEquals("HTTP 403 Forbidden: Access denied by default policy (no applicable rules)", e.getMessage()); + } + + patient = new Patient(); + patient.setId("999"); + patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); + patient.addName().setFamily("Tester").addGiven("Raghad"); + try { + myClient.update().resource(patient).execute(); + fail(); + } catch (ForbiddenOperationException e) { + assertEquals("HTTP 403 Forbidden: Access denied by default policy (no applicable rules)", e.getMessage()); + } + } /** @@ -139,32 +205,32 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource */ @Test public void testDeleteIsAllowedForCompartment() { - + Patient patient = new Patient(); patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); patient.addName().setFamily("Tester").addGiven("Raghad"); final IIdType id = myClient.create().resource(patient).execute().getId(); - + Observation obsInCompartment = new Observation(); obsInCompartment.setStatus(ObservationStatus.FINAL); obsInCompartment.getSubject().setReferenceElement(id.toUnqualifiedVersionless()); IIdType obsInCompartmentId = myClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless(); - + Observation obsNotInCompartment = new Observation(); obsNotInCompartment.setStatus(ObservationStatus.FINAL); IIdType obsNotInCompartmentId = myClient.create().resource(obsNotInCompartment).execute().getId().toUnqualifiedVersionless(); - + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @Override public List buildRuleList(RequestDetails theRequestDetails) { return new RuleBuilder() - .allow().delete().resourcesOfType(Observation.class).inCompartment("Patient", id).andThen() - .deny().delete().allResources().withAnyId().andThen() - .allowAll() - .build(); + .allow().delete().resourcesOfType(Observation.class).inCompartment("Patient", id).andThen() + .deny().delete().allResources().withAnyId().andThen() + .allowAll() + .build(); } }); - + myClient.delete().resourceById(obsInCompartmentId.toUnqualifiedVersionless()).execute(); try { @@ -175,114 +241,38 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource } } + /** + * See #503 + */ @Test - public void testCreateConditional() { - + public void testDeleteIsBlocked() { + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .deny().delete().allResources().withAnyId().andThen() + .allowAll() + .build(); + } + }); + Patient patient = new Patient(); patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); patient.addName().setFamily("Tester").addGiven("Raghad"); - final MethodOutcome output1 = myClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute(); + IIdType id = myClient.create().resource(patient).execute().getId(); - ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - //@formatter:off - return new RuleBuilder() - .allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/" + output1.getId().getIdPart())).andThen() - .allow().updateConditional().allResources() - .build(); - //@formatter:on - } - }); - - patient = new Patient(); - patient.setId(output1.getId().toUnqualifiedVersionless()); - patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); - patient.addName().setFamily("Tester").addGiven("Raghad"); - MethodOutcome output2 = myClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute(); - - assertEquals(output1.getId().getIdPart(), output2.getId().getIdPart()); - - patient = new Patient(); - patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); - patient.addName().setFamily("Tester").addGiven("Raghad"); try { - myClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|101").execute(); - fail(); - } catch (ForbiddenOperationException e) { - assertEquals("HTTP 403 Forbidden: Access denied by default policy (no applicable rules)", e.getMessage()); - } - - patient = new Patient(); - patient.setId("999"); - patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100"); - patient.addName().setFamily("Tester").addGiven("Raghad"); - try { - myClient.update().resource(patient).execute(); - fail(); - } catch (ForbiddenOperationException e) { - assertEquals("HTTP 403 Forbidden: Access denied by default policy (no applicable rules)", e.getMessage()); - } - - } - - /** - * See #667 - */ - @Test - public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException { - Patient pt1 = new Patient(); - pt1.setActive(true); - final IIdType pid1 = myClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless(); - - Patient pt2 = new Patient(); - pt2.setActive(false); - final IIdType pid2 = myClient.create().resource(pt2).execute().getId().toUnqualifiedVersionless(); - - ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - return new RuleBuilder() - .allow().write().allResources().inCompartment("Patient", pid1).andThen() - .build(); - } - }); - - Observation obs = new Observation(); - obs.setStatus(ObservationStatus.FINAL); - obs.setSubject(new Reference(pid1)); - IIdType oid = myClient.create().resource(obs).execute().getId().toUnqualified(); - - - unregisterInterceptors(); - ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - return new RuleBuilder() - .allow().write().allResources().inCompartment("Patient", pid2).andThen() - .build(); - } - }); - - /* - * Try to update to a new patient. The user has access to write to things in - * pid2's compartment, so this would normally be ok, but in this case they are overwriting - * a resource that is already in pid1's compartment, which shouldn't be allowed. - */ - obs = new Observation(); - obs.setId(oid); - obs.setStatus(ObservationStatus.FINAL); - obs.setSubject(new Reference(pid2)); - - try { - myClient.update().resource(obs).execute(); + myClient.delete().resourceById(id.toUnqualifiedVersionless()).execute(); fail(); } catch (ForbiddenOperationException e) { // good } - + + patient = myClient.read().resource(Patient.class).withId(id.toUnqualifiedVersionless()).execute(); + assertEquals(id.getValue(), patient.getId()); } - + @Test public void testDeleteResourceConditional() throws IOException { String methodName = "testDeleteResourceConditional"; @@ -331,7 +321,7 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource //@formatter:on } }); - + HttpDelete delete = new HttpDelete(ourServerBase + "/Patient?name=" + methodName); response = ourHttpClient.execute(delete); try { @@ -350,4 +340,18 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource } + + private void unregisterInterceptors() { + for (IServerInterceptor next : new ArrayList(ourRestServer.getInterceptors())) { + if (next instanceof AuthorizationInterceptor) { + ourRestServer.unregisterInterceptor(next); + } + } + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + } From 9b246852efe6916990e9cf2cd9f5213a3b836263 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 19:45:36 -0500 Subject: [PATCH 19/39] Add test for #762 --- ...tionInterceptorResourceProviderR4Test.java | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java index 7c43f896e51..ab1a109eca2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java @@ -18,11 +18,8 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Observation.ObservationStatus; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.r4.model.Reference; import org.junit.AfterClass; import org.junit.Test; @@ -340,6 +337,45 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource } + /** + * See #762 + */ + @Test + public void testInstanceRuleOkForResourceWithNoId() { + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .deny().write().instance("Patient/123").andThen() + .allowAll() + .build(); + } + }); + + /* + * Create a transaction using linked IDs + */ + + Bundle request = new Bundle(); + request.setType(Bundle.BundleType.TRANSACTION); + + Patient p = new Patient(); + p.setActive(true); + p.setId(IdType.newRandomUuid()); + request.addEntry().setResource(p).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl(p.getId()); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setResource(p); + request.addEntry().setResource(o).getRequest().setMethod(Bundle.HTTPVerb.POST); + + Bundle resp = myClient.transaction().withBundle(request).execute(); + assertEquals(2, resp.getEntry().size()); + + + } + private void unregisterInterceptors() { for (IServerInterceptor next : new ArrayList(ourRestServer.getInterceptors())) { From 9d302fb32311e511c2ab6838279eb5a2f66251ee Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 19:52:05 -0500 Subject: [PATCH 20/39] Credit for #764 --- src/changes/changes.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a00fb9d2921..3d0105c8b64 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -221,6 +221,10 @@ if the delete was permitted via a compartment rule. Thanks to Alvin Leonard for the pull request! + + JAX-RS server module was not able to generate server CapabilityStatement for + some versions of FHIR (DSTU2_HL7ORG, DSTU2_1, or R4). Thanks to Clayton Bodendein for the Pull Request! + From 7d5f4fb71bb71bc0b7b3722ae4c10217afbeb773 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 19:52:05 -0500 Subject: [PATCH 21/39] Credit for #767 --- src/changes/changes.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a00fb9d2921..3d0105c8b64 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -221,6 +221,10 @@ if the delete was permitted via a compartment rule. Thanks to Alvin Leonard for the pull request! + + JAX-RS server module was not able to generate server CapabilityStatement for + some versions of FHIR (DSTU2_HL7ORG, DSTU2_1, or R4). Thanks to Clayton Bodendein for the Pull Request! + From d77c0b959000c7b3457d2cc70424efe09fa29140 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 20:00:09 -0500 Subject: [PATCH 22/39] Credit for #769 --- src/changes/changes.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 3d0105c8b64..e0b95beeaa6 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -225,6 +225,12 @@ JAX-RS server module was not able to generate server CapabilityStatement for some versions of FHIR (DSTU2_HL7ORG, DSTU2_1, or R4). Thanks to Clayton Bodendein for the Pull Request! + + When a server method throws a DataFormatException, the error will now be converted into + an HTTP 400 instead of an HTTP 500 when returned to the client (and a stack + trace will now be returned to the client for JAX-RS server if configured to + do so). Thanks to Clayton Bodendein for the pull request! + From 55d3d81179ae2a29fe2d8a5c31a6c5bc19850366 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 20:03:11 -0500 Subject: [PATCH 23/39] Credit for #770 --- src/changes/changes.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index e0b95beeaa6..7bc6c399c88 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -231,6 +231,11 @@ trace will now be returned to the client for JAX-RS server if configured to do so). Thanks to Clayton Bodendein for the pull request! + + JAX-RS server conformance provider in the example module passed in the + server description, server name, and server version in the incorrect order. + Thanks to Clayton Bodendein for the pull request! + From 150cb33e42750ee5f5e9d4c64d93e800529c2484 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 22 Nov 2017 20:05:37 -0500 Subject: [PATCH 24/39] Credit for #774 --- pom.xml | 4 ++++ src/changes/changes.xml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index aa9b1c4c5e3..83c6f6f1b29 100644 --- a/pom.xml +++ b/pom.xml @@ -378,6 +378,10 @@ JiajingLiang Jiajing Liang + + jamesdaily + James Daily + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 7bc6c399c88..9ef1e0a1fa8 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -236,6 +236,10 @@ server description, server name, and server version in the incorrect order. Thanks to Clayton Bodendein for the pull request! + + The learn more links on the website home page had broken links. Thanks to + James Daily for the pull request to fix this! + From ffac599a308c9455614c7d44bba684b264bd566b Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Thu, 23 Nov 2017 06:42:10 -0500 Subject: [PATCH 25/39] Credit and tests for #762 --- ...tionInterceptorResourceProviderR4Test.java | 87 +++++++++++++++ .../interceptor/IServerInterceptor.java | 24 ++-- .../AuthorizationInterceptorR4Test.java | 103 ++++++++++++++++++ src/changes/changes.xml | 6 + 4 files changed, 211 insertions(+), 9 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java index ab1a109eca2..dc9d4ff4572 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorResourceProviderR4Test.java @@ -376,6 +376,93 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource } + /** + * See #762 + */ + @Test + public void testInstanceRuleOkForResourceWithNoId2() { + + ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen() + .allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen() + .allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen() + .allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen() + .denyAll("deny all") + .build(); + } + }); + + + + // Create a bundle that will be used as a transaction + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.TRANSACTION); + + + + String encounterId = "123-123"; + String encounterSystem = "http://our.internal.code.system/encounter"; + Encounter encounter = new Encounter(); + + encounter.addIdentifier(new Identifier().setValue(encounterId) + .setSystem(encounterSystem)); + + encounter.setStatus(Encounter.EncounterStatus.FINISHED); + + Patient p = new Patient() + .addIdentifier(new Identifier().setValue("321-321").setSystem("http://our.internal.code.system/patient")); + p.setId(IdDt.newRandomUuid()); + + // add patient to bundle so its created + bundle.addEntry() + .setFullUrl(p.getId()) + .setResource(p) + .getRequest() + .setUrl("Patient") + .setMethod(Bundle.HTTPVerb.POST); + + Reference patientRef = new Reference(p.getId()); + + encounter.setSubject(patientRef); + Condition condition = new Condition() + .setCode(new CodeableConcept().addCoding( + new Coding("http://hl7.org/fhir/icd-10", "S53.40", "FOREARM SPRAIN / STRAIN"))) + .setSubject(patientRef); + + condition.setId(IdDt.newRandomUuid()); + + // add condition to bundle so its created + bundle.addEntry() + .setFullUrl(condition.getId()) + .setResource(condition) + .getRequest() + .setUrl("Condition") + .setMethod(Bundle.HTTPVerb.POST); + + Encounter.DiagnosisComponent dc = new Encounter.DiagnosisComponent(); + + dc.setCondition(new Reference(condition.getId())); + encounter.addDiagnosis(dc); + CodeableConcept reason = new CodeableConcept(); + reason.setText("SLIPPED ON FLOOR,PAIN L) ELBOW"); + encounter.addReason(reason); + + // add encounter to bundle so its created + bundle.addEntry() + .setResource(encounter) + .getRequest() + .setUrl("Encounter") + .setIfNoneExist("identifier=" + encounterSystem + "|" + encounterId) + .setMethod(Bundle.HTTPVerb.POST); + + + Bundle resp = myClient.transaction().withBundle(bundle).execute(); + assertEquals(3, resp.getEntry().size()); + + } private void unregisterInterceptors() { for (IServerInterceptor next : new ArrayList(ourRestServer.getInterceptors())) { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java index f2ca2158cee..8b35f6413cb 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java @@ -41,6 +41,8 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import static org.apache.commons.lang3.StringUtils.isBlank; + /** * Provides methods to intercept requests and responses. Note that implementations of this interface may wish to use * {@link InterceptorAdapter} in order to not need to implement every method. @@ -329,9 +331,9 @@ public interface IServerInterceptor { public static class ActionRequestDetails { private final FhirContext myContext; private final IIdType myId; + private final String myResourceType; private RequestDetails myRequestDetails; private IBaseResource myResource; - private final String myResourceType; public ActionRequestDetails(RequestDetails theRequestDetails) { myId = theRequestDetails.getId(); @@ -346,7 +348,11 @@ public interface IServerInterceptor { } public ActionRequestDetails(RequestDetails theRequestDetails, FhirContext theContext, String theResourceType, IIdType theId) { - myId = theId; + if (theId != null && isBlank(theId.getValue())) { + myId = null; + } else { + myId = theId; + } myResourceType = theResourceType; myContext = theContext; myRequestDetails = theRequestDetails; @@ -409,6 +415,13 @@ public interface IServerInterceptor { return myResource; } + /** + * This method should not be called by client code + */ + public void setResource(IBaseResource theObject) { + myResource = theObject; + } + /** * Returns the resource type this request pertains to, or null if this request is not type specific * (e.g. server-history) @@ -450,13 +463,6 @@ public interface IServerInterceptor { } } - /** - * This method should not be called by client code - */ - public void setResource(IBaseResource theObject) { - myResource = theObject; - } - } } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java index 4e40574d9c7..67a887a34f5 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.rest.server.interceptor; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.api.AddProfileTagEnum; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.*; @@ -263,6 +264,108 @@ public class AuthorizationInterceptorR4Test { assertEquals(403, status.getStatusLine().getStatusCode()); } + + /** + * See #762 + */ + @Test + public void testInstanceRuleOkForResourceWithNoId2() throws IOException { + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen() + .allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen() + .allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen() + .allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen() + .denyAll("deny all") + .build(); + } + }); + + + + // Create a bundle that will be used as a transaction + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.TRANSACTION); + + + + String encounterId = "123-123"; + String encounterSystem = "http://our.internal.code.system/encounter"; + Encounter encounter = new Encounter(); + + encounter.addIdentifier(new Identifier().setValue(encounterId) + .setSystem(encounterSystem)); + + encounter.setStatus(Encounter.EncounterStatus.FINISHED); + + Patient p = new Patient() + .addIdentifier(new Identifier().setValue("321-321").setSystem("http://our.internal.code.system/patient")); + p.setId(IdDt.newRandomUuid()); + + // add patient to bundle so its created + bundle.addEntry() + .setFullUrl(p.getId()) + .setResource(p) + .getRequest() + .setUrl("Patient") + .setMethod(Bundle.HTTPVerb.POST); + + Reference patientRef = new Reference(p.getId()); + + encounter.setSubject(patientRef); + Condition condition = new Condition() + .setCode(new CodeableConcept().addCoding( + new Coding("http://hl7.org/fhir/icd-10", "S53.40", "FOREARM SPRAIN / STRAIN"))) + .setSubject(patientRef); + + condition.setId(IdDt.newRandomUuid()); + + // add condition to bundle so its created + bundle.addEntry() + .setFullUrl(condition.getId()) + .setResource(condition) + .getRequest() + .setUrl("Condition") + .setMethod(Bundle.HTTPVerb.POST); + + Encounter.DiagnosisComponent dc = new Encounter.DiagnosisComponent(); + + dc.setCondition(new Reference(condition.getId())); + encounter.addDiagnosis(dc); + CodeableConcept reason = new CodeableConcept(); + reason.setText("SLIPPED ON FLOOR,PAIN L) ELBOW"); + encounter.addReason(reason); + + // add encounter to bundle so its created + bundle.addEntry() + .setResource(encounter) + .getRequest() + .setUrl("Encounter") + .setIfNoneExist("identifier=" + encounterSystem + "|" + encounterId) + .setMethod(Bundle.HTTPVerb.POST); + + Bundle output = new Bundle(); + output.setType(Bundle.BundleType.TRANSACTIONRESPONSE); + output.addEntry() + .setResource(new Patient().setActive(true)) // don't give this an ID + .getResponse().setLocation("/Patient/1"); + + + ourReturn = Collections.singletonList((Resource) output); + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/"); + httpPost.setEntity(createFhirResourceEntity(bundle)); + CloseableHttpResponse status = ourClient.execute(httpPost); + String resp = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + + ourLog.info(resp); + + } + + @Test public void testBatchWhenTransactionReadDenied() throws Exception { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9ef1e0a1fa8..a8e392dae62 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -240,6 +240,12 @@ The learn more links on the website home page had broken links. Thanks to James Daily for the pull request to fix this! + + Prevent a crash in AuthorizationInterceptor when processing transactions + if the interceptor has rules declared which allow resources to be read/written + by "any ID of a given type". Thanks to GitHub user @dconlan for the pull + request! + From db1d2d77cda16e69481fa3c047c2118ee7044f6d Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Thu, 23 Nov 2017 10:40:44 -0500 Subject: [PATCH 26/39] Updated tests --- .../AdditionalRequestHeadersInterceptor.java | 20 ++++++++++++++ .../jaxrs/server/AbstractJaxRsProvider.java | 14 +++++----- .../server/AbstractJaxRsProviderTest.java | 26 ++++++++++++------- .../AuthorizationInterceptorR4Test.java | 2 +- 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java index 840a47f4db4..ac36fb407cc 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.rest.client.interceptor; +/*- + * #%L + * HAPI FHIR - Client Framework + * %% + * Copyright (C) 2014 - 2017 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.rest.client.api.IClientInterceptor; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpResponse; diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index a0194aa1451..4182658a302 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -52,11 +52,11 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { private final FhirContext CTX; /** the http headers */ @Context - private HttpHeaders theHeaders; + private HttpHeaders myHeaders; /** the uri info */ @Context - private UriInfo theUriInfo; + private UriInfo myUriInfo; /** * Default is DSTU2. Use {@link AbstractJaxRsProvider#AbstractJaxRsProvider(FhirContext)} to specify a DSTU3 context. @@ -130,7 +130,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { * @return the headers */ public HttpHeaders getHeaders() { - return this.theHeaders; + return this.myHeaders; } /** @@ -191,7 +191,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { * @return the requestbuilder */ public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation, final String theResourceName) { - return new JaxRsRequest.Builder(this, requestType, restOperation, theUriInfo.getRequestUri().toString(), theResourceName); + return new JaxRsRequest.Builder(this, requestType, restOperation, myUriInfo.getRequestUri().toString(), theResourceName); } /** @@ -212,7 +212,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { * @return the uri info */ public UriInfo getUriInfo() { - return this.theUriInfo; + return this.myUriInfo; } /** @@ -258,7 +258,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { * the headers to set */ public void setHeaders(final HttpHeaders headers) { - this.theHeaders = headers; + this.myHeaders = headers; } /** @@ -268,7 +268,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { * the uri info */ public void setUriInfo(final UriInfo uriInfo) { - this.theUriInfo = uriInfo; + this.myUriInfo = uriInfo; } /** diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderTest.java index e9d15bc5338..4ff13b44da0 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderTest.java @@ -6,6 +6,7 @@ import static org.mockito.Mockito.*; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.HashMap; import javax.ws.rs.core.*; @@ -41,8 +42,16 @@ public class AbstractJaxRsProviderTest { } @Test - public void testWithStackTrace() { - assertFalse(provider.withStackTrace()); + public void testHandleExceptionDataFormatException() throws IOException, URISyntaxException { + final DataFormatException theException = new DataFormatException(); + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getRequestUri()).thenReturn(new URI("http://example.com")); + when(uriInfo.getBaseUri()).thenReturn(new URI("http://example.com")); + when(uriInfo.getQueryParameters()).thenReturn(new MultivaluedHashMap()); + provider.setUriInfo(uriInfo); + final Response result = provider.handleException(theRequest, theException); + assertNotNull(result); + assertEquals(Constants.STATUS_HTTP_400_BAD_REQUEST, result.getStatus()); } @Test @@ -54,14 +63,6 @@ public class AbstractJaxRsProviderTest { assertEquals(base.getStatusCode(), result.getStatus()); } - @Test - public void testHandleExceptionDataFormatException() throws IOException { - final DataFormatException theException = new DataFormatException(); - final Response result = provider.handleException(theRequest, theException); - assertNotNull(result); - assertEquals(Constants.STATUS_HTTP_400_BAD_REQUEST, result.getStatus()); - } - @Test public void testHandleExceptionRuntimeException() throws IOException, URISyntaxException { final RuntimeException theException = new RuntimeException(); @@ -76,4 +77,9 @@ public class AbstractJaxRsProviderTest { assertNotNull(result); assertEquals(Constants.STATUS_HTTP_500_INTERNAL_ERROR, result.getStatus()); } + + @Test + public void testWithStackTrace() { + assertFalse(provider.withStackTrace()); + } } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java index 67a887a34f5..8e633ee2417 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java @@ -268,7 +268,7 @@ public class AuthorizationInterceptorR4Test { /** * See #762 */ - @Test + //@Test public void testInstanceRuleOkForResourceWithNoId2() throws IOException { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { From 4887f18bb3a2588e3a873947fc298ba48c768e84 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 23 Nov 2017 11:05:11 -0500 Subject: [PATCH 27/39] Further refine #762 --- .../auth/AuthorizationInterceptor.java | 272 ++++---- .../server/interceptor/auth/RuleImplOp.java | 10 +- .../AuthorizationInterceptorR4Test.java | 656 +++++++++--------- 3 files changed, 494 insertions(+), 444 deletions(-) diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java index 2d88cda51d8..908e9917af7 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java @@ -20,18 +20,6 @@ package ca.uhn.fhir.rest.server.interceptor.auth; * #L% */ -import static org.apache.commons.lang3.StringUtils.defaultString; - -import java.util.*; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; -import org.hl7.fhir.instance.model.api.*; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; @@ -40,6 +28,21 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; import ca.uhn.fhir.util.CoverageIgnore; +import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.apache.commons.lang3.StringUtils.defaultString; /** * This class is a base class for interceptors which can be used to @@ -66,9 +69,8 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter /** * Constructor - * - * @param theDefaultPolicy - * The default policy if no rules apply (must not be null) + * + * @param theDefaultPolicy The default policy if no rules apply (must not be null) */ public AuthorizationInterceptor(PolicyEnum theDefaultPolicy) { this(); @@ -76,7 +78,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter } private void applyRulesAndFailIfDeny(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, - IBaseResource theOutputResource) { + IBaseResource theOutputResource) { Verdict decision = applyRulesAndReturnDecision(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource); if (decision.getDecision() == PolicyEnum.ALLOW) { @@ -88,7 +90,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter @Override public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, - IBaseResource theOutputResource) { + IBaseResource theOutputResource) { List rules = buildRuleList(theRequestDetails); ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation); @@ -117,9 +119,8 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter * out who the current user is and then using a {@link RuleBuilder} to create * an appropriate rule chain. *

- * - * @param theRequestDetails - * The individual request currently being applied + * + * @param theRequestDetails The individual request currently being applied */ public List buildRuleList(RequestDetails theRequestDetails) { return new ArrayList(); @@ -127,63 +128,63 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter private OperationExamineDirection determineOperationDirection(RestOperationTypeEnum theOperation, IBaseResource theRequestResource) { switch (theOperation) { - case ADD_TAGS: - case DELETE_TAGS: - case GET_TAGS: - // These are DSTU1 operations and not relevant - return OperationExamineDirection.NONE; + case ADD_TAGS: + case DELETE_TAGS: + case GET_TAGS: + // These are DSTU1 operations and not relevant + return OperationExamineDirection.NONE; - case EXTENDED_OPERATION_INSTANCE: - case EXTENDED_OPERATION_SERVER: - case EXTENDED_OPERATION_TYPE: - return OperationExamineDirection.BOTH; + case EXTENDED_OPERATION_INSTANCE: + case EXTENDED_OPERATION_SERVER: + case EXTENDED_OPERATION_TYPE: + return OperationExamineDirection.BOTH; - case METADATA: - // Security does not apply to these operations - return OperationExamineDirection.IN; + case METADATA: + // Security does not apply to these operations + return OperationExamineDirection.IN; - case DELETE: - // Delete is a special case - return OperationExamineDirection.NONE; + case DELETE: + // Delete is a special case + return OperationExamineDirection.NONE; - case CREATE: - case UPDATE: - case PATCH: - // if (theRequestResource != null) { - // if (theRequestResource.getIdElement() != null) { - // if (theRequestResource.getIdElement().hasIdPart() == false) { - // return OperationExamineDirection.IN_UNCATEGORIZED; - // } - // } - // } - return OperationExamineDirection.IN; + case CREATE: + case UPDATE: + case PATCH: + // if (theRequestResource != null) { + // if (theRequestResource.getIdElement() != null) { + // if (theRequestResource.getIdElement().hasIdPart() == false) { + // return OperationExamineDirection.IN_UNCATEGORIZED; + // } + // } + // } + return OperationExamineDirection.IN; - case META: - case META_ADD: - case META_DELETE: - // meta operations do not apply yet - return OperationExamineDirection.NONE; + case META: + case META_ADD: + case META_DELETE: + // meta operations do not apply yet + return OperationExamineDirection.NONE; - case GET_PAGE: - case HISTORY_INSTANCE: - case HISTORY_SYSTEM: - case HISTORY_TYPE: - case READ: - case SEARCH_SYSTEM: - case SEARCH_TYPE: - case VREAD: - return OperationExamineDirection.OUT; + case GET_PAGE: + case HISTORY_INSTANCE: + case HISTORY_SYSTEM: + case HISTORY_TYPE: + case READ: + case SEARCH_SYSTEM: + case SEARCH_TYPE: + case VREAD: + return OperationExamineDirection.OUT; - case TRANSACTION: - return OperationExamineDirection.BOTH; + case TRANSACTION: + return OperationExamineDirection.BOTH; - case VALIDATE: - // Nothing yet - return OperationExamineDirection.NONE; + case VALIDATE: + // Nothing yet + return OperationExamineDirection.NONE; - default: - // Should not happen - throw new IllegalStateException("Unable to apply security to event of type " + theOperation); + default: + // Should not happen + throw new IllegalStateException("Unable to apply security to event of type " + theOperation); } } @@ -195,6 +196,16 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter return myDefaultPolicy; } + /** + * The default policy if no rules have been found to apply. Default value for this setting is {@link PolicyEnum#DENY} + * + * @param theDefaultPolicy The policy (must not be null) + */ + public void setDefaultPolicy(PolicyEnum theDefaultPolicy) { + Validate.notNull(theDefaultPolicy, "theDefaultPolicy must not be null"); + myDefaultPolicy = theDefaultPolicy; + } + /** * Handle an access control verdict of {@link PolicyEnum#DENY}. *

@@ -221,17 +232,17 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter IIdType inputResourceId = null; switch (determineOperationDirection(theOperation, theProcessedRequest.getResource())) { - case IN: - case BOTH: - inputResource = theProcessedRequest.getResource(); - inputResourceId = theProcessedRequest.getId(); - break; - case OUT: - // inputResource = null; - inputResourceId = theProcessedRequest.getId(); - break; - case NONE: - return; + case IN: + case BOTH: + inputResource = theProcessedRequest.getResource(); + inputResourceId = theProcessedRequest.getId(); + break; + case OUT: + // inputResource = null; + inputResourceId = theProcessedRequest.getId(); + break; + case NONE: + return; } RequestDetails requestDetails = theProcessedRequest.getRequestDetails(); @@ -241,43 +252,39 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter @Override public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) { switch (determineOperationDirection(theRequestDetails.getRestOperationType(), null)) { - case IN: - case NONE: - return true; - case BOTH: - case OUT: - break; + case IN: + case NONE: + return true; + case BOTH: + case OUT: + break; } FhirContext fhirContext = theRequestDetails.getServer().getFhirContext(); List resources = Collections.emptyList(); switch (theRequestDetails.getRestOperationType()) { - case SEARCH_SYSTEM: - case SEARCH_TYPE: - case HISTORY_INSTANCE: - case HISTORY_SYSTEM: - case HISTORY_TYPE: - case TRANSACTION: - case GET_PAGE: - case EXTENDED_OPERATION_SERVER: - case EXTENDED_OPERATION_TYPE: - case EXTENDED_OPERATION_INSTANCE: { - if (theResponseObject != null) { - if (theResponseObject instanceof IBaseBundle) { - resources = toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext); - } else if (theResponseObject instanceof IBaseParameters) { + case SEARCH_SYSTEM: + case SEARCH_TYPE: + case HISTORY_INSTANCE: + case HISTORY_SYSTEM: + case HISTORY_TYPE: + case TRANSACTION: + case GET_PAGE: + case EXTENDED_OPERATION_SERVER: + case EXTENDED_OPERATION_TYPE: + case EXTENDED_OPERATION_INSTANCE: { + if (theResponseObject != null) { resources = toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext); } + break; } - break; - } - default: { - if (theResponseObject != null) { - resources = Collections.singletonList(theResponseObject); + default: { + if (theResponseObject != null) { + resources = Collections.singletonList(theResponseObject); + } + break; } - break; - } } for (IBaseResource nextResponse : resources) { @@ -296,7 +303,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter @CoverageIgnore @Override public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) - throws AuthenticationException { + throws AuthenticationException { throw failForDstu1(); } @@ -318,33 +325,38 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter handleUserOperation(theRequest, theNewResource, RestOperationTypeEnum.UPDATE); } - /** - * The default policy if no rules have been found to apply. Default value for this setting is {@link PolicyEnum#DENY} - * - * @param theDefaultPolicy - * The policy (must not be null) - */ - public void setDefaultPolicy(PolicyEnum theDefaultPolicy) { - Validate.notNull(theDefaultPolicy, "theDefaultPolicy must not be null"); - myDefaultPolicy = theDefaultPolicy; - } - - private List toListOfResourcesAndExcludeContainer(IBaseResource theResponseObject, FhirContext fhirContext) { - List resources; - resources = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class); - - // Exclude the container - if (resources.size() > 0 && resources.get(0) == theResponseObject) { - resources = resources.subList(1, resources.size()); - } - - return resources; - } - private static UnsupportedOperationException failForDstu1() { return new UnsupportedOperationException("Use of this interceptor on DSTU1 servers is not supportd"); } + static List toListOfResourcesAndExcludeContainer(IBaseResource theResponseObject, FhirContext fhirContext) { + if (theResponseObject == null) { + return Collections.emptyList(); + } + + List retVal; + + boolean isContainer = false; + if (theResponseObject instanceof IBaseBundle) { + isContainer = true; + } else if (theResponseObject instanceof IBaseParameters) { + isContainer = true; + } + + if (!isContainer) { + return Collections.singletonList(theResponseObject); + } + + retVal = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class); + + // Exclude the container + if (retVal.size() > 0 && retVal.get(0) == theResponseObject) { + retVal = retVal.subList(1, retVal.size()); + } + + return retVal; + } + private enum OperationExamineDirection { BOTH, IN, diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java index a8b8e113053..1d9fb8c838a 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java @@ -165,13 +165,15 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ { } return verdict; } else if (theOutputResource != null) { - List inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource); + + List outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(theOutputResource, theRequestDetails.getFhirContext()); + Verdict verdict = null; - for (BundleEntryParts nextPart : inputResources) { - if (nextPart.getResource() == null) { + for (IBaseResource nextResource : outputResources) { + if (nextResource == null) { continue; } - Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextPart.getResource()); + Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextResource); if (newVerdict == null) { continue; } else if (verdict == null) { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java index 8e633ee2417..86944b35191 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java @@ -52,11 +52,11 @@ import static org.junit.Assert.*; public class AuthorizationInterceptorR4Test { 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(AuthorizationInterceptorR4Test.class); private static CloseableHttpClient ourClient; private static String ourConditionalCreateId; private static FhirContext ourCtx = FhirContext.forR4(); private static boolean ourHitMethod; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AuthorizationInterceptorR4Test.class); private static int ourPort; private static List ourReturn; private static Server ourServer; @@ -112,6 +112,77 @@ public class AuthorizationInterceptorR4Test { return retVal; } + private Bundle createTransactionWithPlaceholdersRequestBundle() { + // Create a input that will be used as a transaction + Bundle input = new Bundle(); + input.setType(Bundle.BundleType.TRANSACTION); + + String encounterId = "123-123"; + String encounterSystem = "http://our.internal.code.system/encounter"; + Encounter encounter = new Encounter(); + + encounter.addIdentifier(new Identifier().setValue(encounterId) + .setSystem(encounterSystem)); + + encounter.setStatus(Encounter.EncounterStatus.FINISHED); + + Patient p = new Patient() + .addIdentifier(new Identifier().setValue("321-321").setSystem("http://our.internal.code.system/patient")); + p.setId(IdDt.newRandomUuid()); + + // add patient to input so its created + input.addEntry() + .setFullUrl(p.getId()) + .setResource(p) + .getRequest() + .setUrl("Patient") + .setMethod(Bundle.HTTPVerb.POST); + + Reference patientRef = new Reference(p.getId()); + + encounter.setSubject(patientRef); + Condition condition = new Condition() + .setCode(new CodeableConcept().addCoding( + new Coding("http://hl7.org/fhir/icd-10", "S53.40", "FOREARM SPRAIN / STRAIN"))) + .setSubject(patientRef); + + condition.setId(IdDt.newRandomUuid()); + + // add condition to input so its created + input.addEntry() + .setFullUrl(condition.getId()) + .setResource(condition) + .getRequest() + .setUrl("Condition") + .setMethod(Bundle.HTTPVerb.POST); + + Encounter.DiagnosisComponent dc = new Encounter.DiagnosisComponent(); + + dc.setCondition(new Reference(condition.getId())); + encounter.addDiagnosis(dc); + CodeableConcept reason = new CodeableConcept(); + reason.setText("SLIPPED ON FLOOR,PAIN L) ELBOW"); + encounter.addReason(reason); + + // add encounter to input so its created + input.addEntry() + .setResource(encounter) + .getRequest() + .setUrl("Encounter") + .setIfNoneExist("identifier=" + encounterSystem + "|" + encounterId) + .setMethod(Bundle.HTTPVerb.POST); + return input; + } + + private Bundle createTransactionWithPlaceholdersResponseBundle() { + Bundle output = new Bundle(); + output.setType(Bundle.BundleType.TRANSACTIONRESPONSE); + output.addEntry() + .setResource(new Patient().setActive(true)) // don't give this an ID + .getResponse().setLocation("/Patient/1"); + return output; + } + private String extractResponseAndClose(HttpResponse status) throws IOException { if (status.getEntity() == null) { return null; @@ -207,7 +278,7 @@ public class AuthorizationInterceptorR4Test { @Override public List buildRuleList(RequestDetails theRequestDetails) { return new RuleBuilder().allow("Rule 1").read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).andThen().denyAll() - .build(); + .build(); } }); @@ -264,108 +335,6 @@ public class AuthorizationInterceptorR4Test { assertEquals(403, status.getStatusLine().getStatusCode()); } - - /** - * See #762 - */ - //@Test - public void testInstanceRuleOkForResourceWithNoId2() throws IOException { - - ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - return new RuleBuilder() - .allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen() - .allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen() - .allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen() - .allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen() - .denyAll("deny all") - .build(); - } - }); - - - - // Create a bundle that will be used as a transaction - Bundle bundle = new Bundle(); - bundle.setType(Bundle.BundleType.TRANSACTION); - - - - String encounterId = "123-123"; - String encounterSystem = "http://our.internal.code.system/encounter"; - Encounter encounter = new Encounter(); - - encounter.addIdentifier(new Identifier().setValue(encounterId) - .setSystem(encounterSystem)); - - encounter.setStatus(Encounter.EncounterStatus.FINISHED); - - Patient p = new Patient() - .addIdentifier(new Identifier().setValue("321-321").setSystem("http://our.internal.code.system/patient")); - p.setId(IdDt.newRandomUuid()); - - // add patient to bundle so its created - bundle.addEntry() - .setFullUrl(p.getId()) - .setResource(p) - .getRequest() - .setUrl("Patient") - .setMethod(Bundle.HTTPVerb.POST); - - Reference patientRef = new Reference(p.getId()); - - encounter.setSubject(patientRef); - Condition condition = new Condition() - .setCode(new CodeableConcept().addCoding( - new Coding("http://hl7.org/fhir/icd-10", "S53.40", "FOREARM SPRAIN / STRAIN"))) - .setSubject(patientRef); - - condition.setId(IdDt.newRandomUuid()); - - // add condition to bundle so its created - bundle.addEntry() - .setFullUrl(condition.getId()) - .setResource(condition) - .getRequest() - .setUrl("Condition") - .setMethod(Bundle.HTTPVerb.POST); - - Encounter.DiagnosisComponent dc = new Encounter.DiagnosisComponent(); - - dc.setCondition(new Reference(condition.getId())); - encounter.addDiagnosis(dc); - CodeableConcept reason = new CodeableConcept(); - reason.setText("SLIPPED ON FLOOR,PAIN L) ELBOW"); - encounter.addReason(reason); - - // add encounter to bundle so its created - bundle.addEntry() - .setResource(encounter) - .getRequest() - .setUrl("Encounter") - .setIfNoneExist("identifier=" + encounterSystem + "|" + encounterId) - .setMethod(Bundle.HTTPVerb.POST); - - Bundle output = new Bundle(); - output.setType(Bundle.BundleType.TRANSACTIONRESPONSE); - output.addEntry() - .setResource(new Patient().setActive(true)) // don't give this an ID - .getResponse().setLocation("/Patient/1"); - - - ourReturn = Collections.singletonList((Resource) output); - HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/"); - httpPost.setEntity(createFhirResourceEntity(bundle)); - CloseableHttpResponse status = ourClient.execute(httpPost); - String resp = extractResponseAndClose(status); - assertEquals(200, status.getStatusLine().getStatusCode()); - - ourLog.info(resp); - - } - - @Test public void testBatchWhenTransactionReadDenied() throws Exception { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @@ -561,7 +530,7 @@ public class AuthorizationInterceptorR4Test { @Override public List buildRuleList(RequestDetails theRequestDetails) { return new RuleBuilder().deny("Rule 1").read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).andThen().allowAll() - .build(); + .build(); } }); @@ -623,6 +592,113 @@ public class AuthorizationInterceptorR4Test { assertTrue(ourHitMethod); } + /** + * See #762 + */ + @Test + public void testTransactionWithPlaceholderIdsResponseUnauthorized() throws IOException { + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen() + .allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen() + .allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen() + .allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen() + .denyAll("deny all") + .build(); + } + }); + + Bundle input = createTransactionWithPlaceholdersRequestBundle(); + Bundle output = createTransactionWithPlaceholdersResponseBundle(); + + ourReturn = Collections.singletonList((Resource) output); + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/"); + httpPost.setEntity(createFhirResourceEntity(input)); + CloseableHttpResponse status = ourClient.execute(httpPost); + String resp = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + + ourLog.info(resp); + + } + + /** + * See #762 + */ + @Test + public void testTransactionWithPlaceholderIdsResponseAuthorized() throws IOException { + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen() + .allow("read patient").read().resourcesOfType(Patient.class).withAnyId().andThen() + .allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen() + .allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen() + .allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen() + .denyAll("deny all") + .build(); + } + }); + + Bundle input = createTransactionWithPlaceholdersRequestBundle(); + Bundle output = createTransactionWithPlaceholdersResponseBundle(); + + ourReturn = Collections.singletonList((Resource) output); + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/"); + httpPost.setEntity(createFhirResourceEntity(input)); + CloseableHttpResponse status = ourClient.execute(httpPost); + String resp = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + + ourLog.info(resp); + + } + + @Test + public void testInvalidInstanceIds() throws Exception { + try { + new RuleBuilder().allow("Rule 1").write().instance((String) null); + fail(); + } catch (NullPointerException e) { + assertEquals("theId must not be null or empty", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance(""); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("theId must not be null or empty", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance("Observation/"); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("theId must contain an ID part", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance(new IdType()); + fail(); + } catch (NullPointerException e) { + assertEquals("theId.getValue() must not be null or empty", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance(new IdType("")); + fail(); + } catch (NullPointerException e) { + assertEquals("theId.getValue() must not be null or empty", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance(new IdType("Observation", (String) null)); + fail(); + } catch (NullPointerException e) { + assertEquals("theId must contain an ID part", e.getMessage()); + } + } + @Test public void testMetadataAllow() throws Exception { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @@ -865,8 +941,8 @@ public class AuthorizationInterceptorR4Test { @Override public List buildRuleList(RequestDetails theRequestDetails) { return new RuleBuilder() - .allow("RULE 1").operation().named("opName").onAnyInstance().andThen() - .build(); + .allow("RULE 1").operation().named("opName").onAnyInstance().andThen() + .build(); } }); @@ -1272,102 +1348,6 @@ public class AuthorizationInterceptorR4Test { } - @Test - public void testReadPageRight() throws Exception { - ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - return new RuleBuilder() - .allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")) - .build(); - } - }); - - HttpGet httpGet; - HttpResponse status; - String respString; - Bundle respBundle; - - ourReturn = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - ourReturn.add(createPatient(1)); - } - - ourHitMethod = false; - httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); - status = ourClient.execute(httpGet); - respString = extractResponseAndClose(status); - assertEquals(200, status.getStatusLine().getStatusCode()); - assertTrue(ourHitMethod); - respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); - assertEquals(5, respBundle.getEntry().size()); - assertEquals(10, respBundle.getTotal()); - assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); - assertNotNull(respBundle.getLink("next")); - - // Load next page - - ourHitMethod = false; - httpGet = new HttpGet(respBundle.getLink("next").getUrl()); - status = ourClient.execute(httpGet); - respString = extractResponseAndClose(status); - assertEquals(200, status.getStatusLine().getStatusCode()); - assertFalse(ourHitMethod); - respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); - assertEquals(5, respBundle.getEntry().size()); - assertEquals(10, respBundle.getTotal()); - assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); - assertNull(respBundle.getLink("next")); - - } - - @Test - public void testReadPageWrong() throws Exception { - ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - return new RuleBuilder() - .allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")) - .build(); - } - }); - - HttpGet httpGet; - HttpResponse status; - String respString; - Bundle respBundle; - - ourReturn = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - ourReturn.add(createPatient(1)); - } - for (int i = 0; i < 5; i++) { - ourReturn.add(createPatient(2)); - } - - ourHitMethod = false; - httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); - status = ourClient.execute(httpGet); - respString = extractResponseAndClose(status); - assertEquals(200, status.getStatusLine().getStatusCode()); - assertTrue(ourHitMethod); - respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); - assertEquals(5, respBundle.getEntry().size()); - assertEquals(10, respBundle.getTotal()); - assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); - assertNotNull(respBundle.getLink("next")); - - // Load next page - - ourHitMethod = false; - httpGet = new HttpGet(respBundle.getLink("next").getUrl()); - status = ourClient.execute(httpGet); - respString = extractResponseAndClose(status); - assertEquals(403, status.getStatusLine().getStatusCode()); - assertFalse(ourHitMethod); - - } - @Test public void testReadByCompartmentWrong() throws Exception { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @@ -1436,6 +1416,147 @@ public class AuthorizationInterceptorR4Test { } + @Test + public void testReadByInstance() throws Exception { + ourConditionalCreateId = "1"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().instance("Observation/900").andThen() + .allow("Rule 1").read().instance("901").andThen() + .build(); + } + }); + + HttpResponse status; + String response; + HttpGet httpGet; + + ourReturn = Collections.singletonList(createObservation(900, "Patient/1")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/900"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Collections.singletonList(createPatient(901)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/901"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Collections.singletonList(createPatient(1)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=json"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertFalse(ourHitMethod); + + } + + @Test + public void testReadPageRight() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String respString; + Bundle respBundle; + + ourReturn = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + ourReturn.add(createPatient(1)); + } + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); + status = ourClient.execute(httpGet); + respString = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(10, respBundle.getTotal()); + assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + assertNotNull(respBundle.getLink("next")); + + // Load next page + + ourHitMethod = false; + httpGet = new HttpGet(respBundle.getLink("next").getUrl()); + status = ourClient.execute(httpGet); + respString = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(10, respBundle.getTotal()); + assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + assertNull(respBundle.getLink("next")); + + } + + @Test + public void testReadPageWrong() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String respString; + Bundle respBundle; + + ourReturn = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + ourReturn.add(createPatient(1)); + } + for (int i = 0; i < 5; i++) { + ourReturn.add(createPatient(2)); + } + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); + status = ourClient.execute(httpGet); + respString = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(10, respBundle.getTotal()); + assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + assertNotNull(respBundle.getLink("next")); + + // Load next page + + ourHitMethod = false; + httpGet = new HttpGet(respBundle.getLink("next").getUrl()); + status = ourClient.execute(httpGet); + respString = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + } + @Test public void testTransactionWriteGood() throws Exception { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @@ -1818,82 +1939,6 @@ public class AuthorizationInterceptorR4Test { } - @Test - public void testInvalidInstanceIds() throws Exception { - try { - new RuleBuilder().allow("Rule 1").write().instance((String) null); - fail(); - } catch (NullPointerException e) { - assertEquals("theId must not be null or empty", e.getMessage()); - } - try { - new RuleBuilder().allow("Rule 1").write().instance(""); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("theId must not be null or empty", e.getMessage()); - } - try { - new RuleBuilder().allow("Rule 1").write().instance("Observation/"); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("theId must contain an ID part", e.getMessage()); - } - try { - new RuleBuilder().allow("Rule 1").write().instance(new IdType()); - fail(); - } catch (NullPointerException e) { - assertEquals("theId.getValue() must not be null or empty", e.getMessage()); - } - try { - new RuleBuilder().allow("Rule 1").write().instance(new IdType("")); - fail(); - } catch (NullPointerException e) { - assertEquals("theId.getValue() must not be null or empty", e.getMessage()); - } - try { - new RuleBuilder().allow("Rule 1").write().instance(new IdType("Observation", (String) null)); - fail(); - } catch (NullPointerException e) { - assertEquals("theId must contain an ID part", e.getMessage()); - } - } - - @Test - public void testWritePatchByInstance() throws Exception { - ourConditionalCreateId = "1"; - - ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - return new RuleBuilder() - .allow("Rule 1").write().instance("Patient/900").andThen() - .build(); - } - }); - - HttpEntityEnclosingRequestBase httpPost; - HttpResponse status; - String response; - - String input = "[ { \"op\": \"replace\", \"path\": \"/gender\", \"value\": \"male\" } ]"; - - ourHitMethod = false; - httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/900"); - httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json"))); - status = ourClient.execute(httpPost); - response = extractResponseAndClose(status); - assertEquals(204, status.getStatusLine().getStatusCode()); - assertTrue(ourHitMethod); - - ourHitMethod = false; - httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/999"); - httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json"))); - status = ourClient.execute(httpPost); - response = extractResponseAndClose(status); - assertEquals(403, status.getStatusLine().getStatusCode()); - assertFalse(ourHitMethod); - } - @Test public void testWriteByInstance() throws Exception { ourConditionalCreateId = "1"; @@ -1949,48 +1994,39 @@ public class AuthorizationInterceptorR4Test { } @Test - public void testReadByInstance() throws Exception { + public void testWritePatchByInstance() throws Exception { ourConditionalCreateId = "1"; ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { @Override public List buildRuleList(RequestDetails theRequestDetails) { return new RuleBuilder() - .allow("Rule 1").read().instance("Observation/900").andThen() - .allow("Rule 1").read().instance("901").andThen() + .allow("Rule 1").write().instance("Patient/900").andThen() .build(); } }); + HttpEntityEnclosingRequestBase httpPost; HttpResponse status; String response; - HttpGet httpGet; - ourReturn = Collections.singletonList(createObservation(900, "Patient/1")); + String input = "[ { \"op\": \"replace\", \"path\": \"/gender\", \"value\": \"male\" } ]"; + ourHitMethod = false; - httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/900"); - status = ourClient.execute(httpGet); + httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/900"); + httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json"))); + status = ourClient.execute(httpPost); response = extractResponseAndClose(status); - assertEquals(200, status.getStatusLine().getStatusCode()); + assertEquals(204, status.getStatusLine().getStatusCode()); assertTrue(ourHitMethod); - ourReturn = Collections.singletonList(createPatient(901)); ourHitMethod = false; - httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/901"); - status = ourClient.execute(httpGet); - response = extractResponseAndClose(status); - assertEquals(200, status.getStatusLine().getStatusCode()); - assertTrue(ourHitMethod); - - ourReturn = Collections.singletonList(createPatient(1)); - ourHitMethod = false; - httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=json"); - status = ourClient.execute(httpGet); + httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/999"); + httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json"))); + status = ourClient.execute(httpPost); response = extractResponseAndClose(status); assertEquals(403, status.getStatusLine().getStatusCode()); - assertEquals(ERR403, response); assertFalse(ourHitMethod); - } @AfterClass @@ -2220,6 +2256,14 @@ public class AuthorizationInterceptorR4Test { return (Parameters) new Parameters().setId("1"); } + @Patch() + public MethodOutcome patch(@IdParam IdType theId, @ResourceParam String theResource, PatchTypeEnum thePatchType) { + ourHitMethod = true; + + MethodOutcome retVal = new MethodOutcome(); + return retVal; + } + @Read(version = true) public Patient read(@IdParam IdType theId) { ourHitMethod = true; @@ -2252,17 +2296,9 @@ public class AuthorizationInterceptorR4Test { return retVal; } - @Patch() - public MethodOutcome patch(@IdParam IdType theId, @ResourceParam String theResource, PatchTypeEnum thePatchType) { - ourHitMethod = true; - - MethodOutcome retVal = new MethodOutcome(); - return retVal; - } - @Validate public MethodOutcome validate(@ResourceParam Patient theResource, @IdParam IdType theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, - @Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile, RequestDetails theRequestDetails) { + @Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile, RequestDetails theRequestDetails) { ourHitMethod = true; OperationOutcome oo = new OperationOutcome(); oo.addIssue().setDiagnostics("OK"); @@ -2271,7 +2307,7 @@ public class AuthorizationInterceptorR4Test { @Validate public MethodOutcome validate(@ResourceParam Patient theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode, - @Validate.Profile String theProfile, RequestDetails theRequestDetails) { + @Validate.Profile String theProfile, RequestDetails theRequestDetails) { ourHitMethod = true; OperationOutcome oo = new OperationOutcome(); oo.addIssue().setDiagnostics("OK"); From fc991a33339583ccd347be541680e8c397acd31c Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 23 Nov 2017 11:36:20 -0500 Subject: [PATCH 28/39] Fix tests --- .../ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java | 2 +- .../java/ca/uhn/fhir/jpa/dao/dstu2/FhirSystemDaoDstu2Test.java | 2 +- .../ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java | 2 +- .../java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java | 2 +- .../test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java | 2 +- .../test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java index 1c3a5d470fc..f725737ef79 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java @@ -461,7 +461,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { ArgumentCaptor detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture()); ActionRequestDetails details = detailsCapt.getValue(); - assertNotNull(details.getId()); + assertNull(details.getId()); assertEquals("Patient", details.getResourceType()); assertEquals(Patient.class, details.getResource().getClass()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSystemDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSystemDaoDstu2Test.java index b8121aaa6e5..607c522896b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSystemDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSystemDaoDstu2Test.java @@ -573,7 +573,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2SystemTest { detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture()); details = detailsCapt.getValue(); - assertNotNull(details.getId()); + assertNull(details.getId()); assertEquals("Patient", details.getResourceType()); assertEquals(Patient.class, details.getResource().getClass()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java index 83c959caf68..197009d507e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java @@ -688,7 +688,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { ArgumentCaptor detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture()); ActionRequestDetails details = detailsCapt.getValue(); - assertNotNull(details.getId()); + assertNull(details.getId()); assertEquals("Patient", details.getResourceType()); assertEquals(Patient.class, details.getResource().getClass()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java index 42cd0ea6625..df119dca4a9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java @@ -939,7 +939,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture()); details = detailsCapt.getValue(); - assertNotNull(details.getId()); + assertNull(details.getId()); assertEquals("Patient", details.getResourceType()); assertEquals(Patient.class, details.getResource().getClass()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java index 04a686da42c..73ccd4b967f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java @@ -730,7 +730,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { ArgumentCaptor detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture()); ActionRequestDetails details = detailsCapt.getValue(); - assertNotNull(details.getId()); + assertNull(details.getId()); assertEquals("Patient", details.getResourceType()); assertEquals(Patient.class, details.getResource().getClass()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java index 4997c46e92d..6e695b7a1fd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java @@ -1018,7 +1018,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture()); details = detailsCapt.getValue(); - assertNotNull(details.getId()); + assertNull(details.getId()); assertEquals("Patient", details.getResourceType()); assertEquals(Patient.class, details.getResource().getClass()); From 94be52304b642701c5fe77b8631816bd61528b44 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Thu, 23 Nov 2017 11:50:30 -0500 Subject: [PATCH 29/39] Fix tests --- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 2 +- .../search/LuceneSearchMappingFactory.java | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 4f88d75eb5b..772638bb774 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -2015,7 +2015,7 @@ public abstract class BaseHapiFhirDao implements IDao { /* * The following block of code is used to strip out diacritical marks from latin script - * and also convert to upper case. E.g. "jåmes" becomes "JAMES". + * and also convert to upper case. E.g. "j?mes" becomes "JAMES". * * See http://www.unicode.org/charts/PDF/U0300.pdf for the logic * behind stripping 0300-036F diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java index 1f950f28ed4..2a4493d1b87 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.search; +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2017 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 org.apache.lucene.analysis.core.LowerCaseFilterFactory; import org.apache.lucene.analysis.core.StopFilterFactory; import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory; From 1c898d39267a5905e3d9409f713ecbe61faef464 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 23 Nov 2017 13:23:02 -0500 Subject: [PATCH 30/39] Deploy artifacts for spring boot --- hapi-fhir-spring-boot/pom.xml | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index a266e1653ed..df76b660c5a 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -1,21 +1,21 @@ - - 4.0.0 - - - ca.uhn.hapi.fhir - hapi-fhir - 3.1.0-SNAPSHOT - ../pom.xml - - - hapi-fhir-spring-boot - pom - - - hapi-fhir-spring-boot-autoconfigure - hapi-fhir-spring-boot-starter - hapi-fhir-spring-boot-samples - - - \ No newline at end of file + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-deployable-pom + 3.1.0-SNAPSHOT + ../hapi-deployable-pom/pom.xml + + + hapi-fhir-spring-boot + pom + + + hapi-fhir-spring-boot-autoconfigure + hapi-fhir-spring-boot-starter + hapi-fhir-spring-boot-samples + + + From 42dd34252d29767343b6818fb66f2c14a52d102c Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Thu, 23 Nov 2017 13:27:21 -0500 Subject: [PATCH 31/39] Version bump to 3.1.0 --- .../hapi-fhir-base-example-embedded-ws/pom.xml | 2 +- .../hapi-fhir-standalone-overlay-example/pom.xml | 2 +- examples/pom.xml | 2 +- hapi-deployable-pom/pom.xml | 2 +- hapi-fhir-android/pom.xml | 2 +- hapi-fhir-base-test-mindeps-client/pom.xml | 2 +- hapi-fhir-base-test-mindeps-server/pom.xml | 2 +- hapi-fhir-base/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-app/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml | 2 +- hapi-fhir-cli/pom.xml | 2 +- hapi-fhir-client-okhttp/pom.xml | 2 +- hapi-fhir-client/pom.xml | 2 +- hapi-fhir-converter/pom.xml | 16 ++++++++-------- hapi-fhir-dist/pom.xml | 2 +- hapi-fhir-igpacks/pom.xml | 2 +- hapi-fhir-jacoco/pom.xml | 2 +- hapi-fhir-jaxrsserver-base/pom.xml | 2 +- hapi-fhir-jaxrsserver-example/pom.xml | 2 +- hapi-fhir-jpaserver-base/pom.xml | 2 +- hapi-fhir-jpaserver-example/pom.xml | 2 +- hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 2 +- hapi-fhir-server/pom.xml | 2 +- .../hapi-fhir-spring-boot-autoconfigure/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../hapi-fhir-spring-boot-samples/pom.xml | 2 +- .../hapi-fhir-spring-boot-starter/pom.xml | 2 +- hapi-fhir-spring-boot/pom.xml | 2 +- hapi-fhir-structures-dstu2.1/pom.xml | 2 +- hapi-fhir-structures-dstu2/pom.xml | 2 +- hapi-fhir-structures-dstu3/pom.xml | 2 +- hapi-fhir-structures-hl7org-dstu2/pom.xml | 2 +- hapi-fhir-structures-r4/pom.xml | 2 +- hapi-fhir-testpage-overlay/pom.xml | 2 +- hapi-fhir-utilities/pom.xml | 2 +- hapi-fhir-validation-resources-dstu2.1/pom.xml | 2 +- hapi-fhir-validation-resources-dstu2/pom.xml | 2 +- hapi-fhir-validation-resources-dstu3/pom.xml | 2 +- hapi-fhir-validation-resources-r4/pom.xml | 2 +- hapi-fhir-validation/pom.xml | 2 +- hapi-tinder-plugin/pom.xml | 2 +- hapi-tinder-test/pom.xml | 2 +- pom.xml | 2 +- restful-server-example-test/pom.xml | 2 +- restful-server-example/pom.xml | 2 +- 48 files changed, 55 insertions(+), 55 deletions(-) diff --git a/example-projects/hapi-fhir-base-example-embedded-ws/pom.xml b/example-projects/hapi-fhir-base-example-embedded-ws/pom.xml index 658cb5b114e..a82ddc74d7d 100644 --- a/example-projects/hapi-fhir-base-example-embedded-ws/pom.xml +++ b/example-projects/hapi-fhir-base-example-embedded-ws/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../../pom.xml diff --git a/example-projects/hapi-fhir-standalone-overlay-example/pom.xml b/example-projects/hapi-fhir-standalone-overlay-example/pom.xml index ffdba362668..d363b6ff71f 100644 --- a/example-projects/hapi-fhir-standalone-overlay-example/pom.xml +++ b/example-projects/hapi-fhir-standalone-overlay-example/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../../pom.xml hapi-fhir-standalone-overlay-example diff --git a/examples/pom.xml b/examples/pom.xml index 9e6fd9ab937..1e2b3d5ef7c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 93b7bdaed96..1964633b0c9 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 5437fbfd16b..7c4b00c2184 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base-test-mindeps-client/pom.xml b/hapi-fhir-base-test-mindeps-client/pom.xml index d069f0f6ff5..845e3cef16b 100644 --- a/hapi-fhir-base-test-mindeps-client/pom.xml +++ b/hapi-fhir-base-test-mindeps-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-base-test-mindeps-server/pom.xml b/hapi-fhir-base-test-mindeps-server/pom.xml index c5ffff5ca18..8499d2c242b 100644 --- a/hapi-fhir-base-test-mindeps-server/pom.xml +++ b/hapi-fhir-base-test-mindeps-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index 87536543923..601a3050870 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index 6a53c471610..424366e4539 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml index 44d492c5558..b42a1aea0a1 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index 6175d0fe639..16fe94d21ea 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index d45e99e0e57..9ea4bbb7424 100644 --- a/hapi-fhir-client-okhttp/pom.xml +++ b/hapi-fhir-client-okhttp/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index 9fa57d60ce4..bcdecfa65ec 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index fae3b779bf7..918cb8a8019 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml @@ -26,42 +26,42 @@ ca.uhn.hapi.fhir hapi-fhir-base - 3.1.0-SNAPSHOT + 3.1.0 ca.uhn.hapi.fhir hapi-fhir-structures-hl7org-dstu2 - 3.1.0-SNAPSHOT + 3.1.0 true ca.uhn.hapi.fhir hapi-fhir-structures-dstu2.1 - 3.1.0-SNAPSHOT + 3.1.0 true ca.uhn.hapi.fhir hapi-fhir-structures-dstu3 - 3.1.0-SNAPSHOT + 3.1.0 true ca.uhn.hapi.fhir hapi-fhir-structures-r4 - 3.1.0-SNAPSHOT + 3.1.0 true ca.uhn.hapi.fhir hapi-fhir-validation-resources-dstu2 - 3.1.0-SNAPSHOT + 3.1.0 true ca.uhn.hapi.fhir hapi-fhir-validation-resources-dstu3 - 3.1.0-SNAPSHOT + 3.1.0 true diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index a7499e3b01b..5b852e7b0bf 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-igpacks/pom.xml b/hapi-fhir-igpacks/pom.xml index 0f7dea425bc..0b9c3bf600c 100644 --- a/hapi-fhir-igpacks/pom.xml +++ b/hapi-fhir-igpacks/pom.xml @@ -5,7 +5,7 @@ hapi-deployable-pom ca.uhn.hapi.fhir - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 4069846907f..12353bc1c4a 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index eb31a66e9a1..33b7102d8bf 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-example/pom.xml b/hapi-fhir-jaxrsserver-example/pom.xml index b533f39c2a4..309afd494c1 100644 --- a/hapi-fhir-jaxrsserver-example/pom.xml +++ b/hapi-fhir-jaxrsserver-example/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 1a68b4c4b84..4d17be5f824 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-example/pom.xml b/hapi-fhir-jpaserver-example/pom.xml index 80f93ff7f33..bd9cab4832c 100644 --- a/hapi-fhir-jpaserver-example/pom.xml +++ b/hapi-fhir-jpaserver-example/pom.xml @@ -10,7 +10,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index c7ae6d7a63a..0f0456e49db 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index 30b505c8021..60ba936b544 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index b66479550f3..65a45c4bca9 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 3.1.0-SNAPSHOT + 3.1.0 hapi-fhir-spring-boot-autoconfigure diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml index 963adda0fcb..7fb0d86eb43 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 3.1.0-SNAPSHOT + 3.1.0 hapi-fhir-spring-boot-sample-client-apache diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml index 69cc6e9f6b1..65834d64005 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 3.1.0-SNAPSHOT + 3.1.0 hapi-fhir-spring-boot-sample-client-okhttp diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index 439a5ce9252..5e04d3b1b4f 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 3.1.0-SNAPSHOT + 3.1.0 hapi-fhir-spring-boot-sample-server-jersey diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml index a7ecaf84aa2..257858e99e3 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 3.1.0-SNAPSHOT + 3.1.0 hapi-fhir-spring-boot-sample-server-jpa diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index 29511655859..9c0e2a05bfc 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 3.1.0-SNAPSHOT + 3.1.0 hapi-fhir-spring-boot-samples diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index 094a37377e3..673d0f59e54 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 3.1.0-SNAPSHOT + 3.1.0 hapi-fhir-spring-boot-starter diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index a266e1653ed..98694c6a835 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index 47968c3f638..42946f38e96 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index 1028391e59d..023d6f67f0b 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index d796ef60380..70ac19c4efe 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index 9e3218868b8..5d2169f3335 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index 686617184cc..c5df2b5e794 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index 5bf1043b4c2..a3ed3ca2e20 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-fhir-utilities/pom.xml b/hapi-fhir-utilities/pom.xml index cef6147a78d..71e6e02f874 100644 --- a/hapi-fhir-utilities/pom.xml +++ b/hapi-fhir-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index 21798311279..9640637b5b7 100644 --- a/hapi-fhir-validation-resources-dstu2.1/pom.xml +++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml index 919bfc833a7..ec81d9c8924 100644 --- a/hapi-fhir-validation-resources-dstu2/pom.xml +++ b/hapi-fhir-validation-resources-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml index 861eae57143..6c1bd54e755 100644 --- a/hapi-fhir-validation-resources-dstu3/pom.xml +++ b/hapi-fhir-validation-resources-dstu3/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml index 21fa31db4b1..c830e932f90 100644 --- a/hapi-fhir-validation-resources-r4/pom.xml +++ b/hapi-fhir-validation-resources-r4/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 9a6988b95b3..b4d07c13504 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.1.0-SNAPSHOT + 3.1.0 ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 07daa6c841b..63970fdd57d 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index 934b55aa083..8e6e255cf8c 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/pom.xml b/pom.xml index 83c6f6f1b29..d780bda406b 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 3.1.0-SNAPSHOT + 3.1.0 HAPI-FHIR https://hapifhir.io diff --git a/restful-server-example-test/pom.xml b/restful-server-example-test/pom.xml index 2738c6af058..3dc14357038 100644 --- a/restful-server-example-test/pom.xml +++ b/restful-server-example-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml diff --git a/restful-server-example/pom.xml b/restful-server-example/pom.xml index 309e9310235..fabc76fda71 100644 --- a/restful-server-example/pom.xml +++ b/restful-server-example/pom.xml @@ -8,7 +8,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.1.0-SNAPSHOT + 3.1.0 ../pom.xml From 9951c47ddc63e688733332e9f17f3ca61c47c3ba Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Thu, 23 Nov 2017 13:28:34 -0500 Subject: [PATCH 32/39] Use correct super pom --- hapi-fhir-spring-boot/pom.xml | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 8eeb52b276b..6b8f7d477e5 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -1,11 +1,10 @@ -<<<<<<< HEAD 4.0.0 ca.uhn.hapi.fhir - hapi-fhir + hapi-deployable-pom 3.1.0 ../pom.xml @@ -20,26 +19,4 @@ -======= - - 4.0.0 - - ca.uhn.hapi.fhir - hapi-deployable-pom - 3.1.0-SNAPSHOT - ../hapi-deployable-pom/pom.xml - - - hapi-fhir-spring-boot - pom - - - hapi-fhir-spring-boot-autoconfigure - hapi-fhir-spring-boot-starter - hapi-fhir-spring-boot-samples - - - ->>>>>>> 9aea5569def20b9321a9b2e4c23fd93877c210cd From 3d3b787db73804e9d979e86628fecbffc3630805 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Thu, 23 Nov 2017 15:06:41 -0500 Subject: [PATCH 33/39] Version bump to 3.1.0 --- .../pom.xml | 237 +++---- .../autoconfigure/FhirAutoConfiguration.java | 595 +++++++++--------- .../boot/autoconfigure/FhirProperties.java | 191 +++--- .../FhirRestfulServerCustomizer.java | 20 + .../SampleApacheRestfulClientApplication.java | 20 + .../SampleOkHttpRestfulClientApplication.java | 20 + .../pom.xml | 152 +++-- .../SampleJerseyRestfulServerApplication.java | 20 + .../provider/PatientResourceProvider.java | 20 + .../hapi-fhir-spring-boot-starter/pom.xml | 55 +- hapi-fhir-spring-boot/pom.xml | 14 +- pom.xml | 4 +- 12 files changed, 761 insertions(+), 587 deletions(-) diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index 65a45c4bca9..d86cfcabd3a 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -1,118 +1,119 @@ - - 4.0.0 - - - ca.uhn.hapi.fhir - hapi-fhir-spring-boot - 3.1.0 - - - hapi-fhir-spring-boot-autoconfigure - - jar - - - - - org.springframework.boot - spring-boot-autoconfigure - - - - - ca.uhn.hapi.fhir - hapi-fhir-base - ${project.version} - true - - - ca.uhn.hapi.fhir - hapi-fhir-server - ${project.version} - true - - - ca.uhn.hapi.fhir - hapi-fhir-jpaserver-base - ${project.version} - true - - - ca.uhn.hapi.fhir - hapi-fhir-jaxrsserver-base - ${project.version} - true - - - ca.uhn.hapi.fhir - hapi-fhir-client - ${project.version} - true - - - ca.uhn.hapi.fhir - hapi-fhir-client-okhttp - ${project.version} - true - - - javax.servlet - javax.servlet-api - true - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework - spring-web - test - - - com.h2database - h2 - test - - - ch.qos.logback - logback-classic - test - - - org.slf4j - log4j-over-slf4j - test - - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu2 - ${project.version} - test - - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu2.1 - ${project.version} - test - - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu3 - ${project.version} - test - - - - \ No newline at end of file + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-deployable-pom + 3.1.0 + ../../hapi-deployable-pom/pom.xml + + + hapi-fhir-spring-boot-autoconfigure + + jar + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + ca.uhn.hapi.fhir + hapi-fhir-base + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-server + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-base + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-client + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-client-okhttp + ${project.version} + true + + + javax.servlet + javax.servlet-api + true + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework + spring-web + test + + + com.h2database + h2 + test + + + ch.qos.logback + logback-classic + test + + + org.slf4j + log4j-over-slf4j + test + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu2 + ${project.version} + test + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu2.1 + ${project.version} + test + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu3 + ${project.version} + test + + + + diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java index 3a108336b07..79c3890cd0f 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java @@ -1,287 +1,308 @@ -package ca.uhn.fhir.spring.boot.autoconfigure; - -import java.util.List; - -import javax.servlet.ServletException; -import javax.sql.DataSource; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.provider.BaseJpaProvider; -import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider; -import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; -import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory; -import ca.uhn.fhir.rest.client.api.IClientInterceptor; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.client.api.IRestfulClientFactory; -import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; -import ca.uhn.fhir.rest.server.IPagingProvider; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; -import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; -import okhttp3.OkHttpClient; -import org.apache.http.client.HttpClient; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ResourceCondition; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.util.CollectionUtils; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for HAPI FHIR. - * - * @author Mathieu Ouellet - */ -@Configuration -@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) -@EnableConfigurationProperties(FhirProperties.class) -public class FhirAutoConfiguration { - - private final FhirProperties properties; - - public FhirAutoConfiguration(FhirProperties properties) { - this.properties = properties; - } - - @Bean - @ConditionalOnMissingBean - public FhirContext fhirContext() { - FhirContext fhirContext = new FhirContext(properties.getVersion()); - return fhirContext; - } - - @Configuration - @ConditionalOnClass(AbstractJaxRsProvider.class) - @EnableConfigurationProperties(FhirProperties.class) - @ConfigurationProperties("hapi.fhir.rest") - @SuppressWarnings("serial") - static class FhirRestfulServerConfiguration extends RestfulServer { - - private final FhirProperties properties; - - private final FhirContext fhirContext; - - private final List resourceProviders; - - private final IPagingProvider pagingProvider; - - private final List interceptors; - - private final List customizers; - - public FhirRestfulServerConfiguration( - FhirProperties properties, - FhirContext fhirContext, - ObjectProvider> resourceProviders, - ObjectProvider pagingProvider, - ObjectProvider> interceptors, - ObjectProvider> customizers) { - this.properties = properties; - this.fhirContext = fhirContext; - this.resourceProviders = resourceProviders.getIfAvailable(); - this.pagingProvider = pagingProvider.getIfAvailable(); - this.interceptors = interceptors.getIfAvailable(); - this.customizers = customizers.getIfAvailable(); - } - - @Bean - public ServletRegistrationBean fhirServerRegistrationBean() { - ServletRegistrationBean registration = new ServletRegistrationBean(this, this.properties.getServer().getPath()); - registration.setLoadOnStartup(1); - return registration; - } - - @Override - protected void initialize() throws ServletException { - super.initialize(); - - setFhirContext(this.fhirContext); - setResourceProviders(this.resourceProviders); - setPagingProvider(this.pagingProvider); - setInterceptors(this.interceptors); - - setServerAddressStrategy(new HardcodedServerAddressStrategy(this.properties.getServer().getPath())); - - customize(); - } - - private void customize() { - if (this.customizers != null) { - AnnotationAwareOrderComparator.sort(this.customizers); - for (FhirRestfulServerCustomizer customizer : this.customizers) { - customizer.customize(this); - } - } - } - } - - @Configuration - @ConditionalOnClass(BaseJpaProvider.class) - @ConditionalOnBean(DataSource.class) - @EnableConfigurationProperties(FhirProperties.class) - static class FhirJpaServerConfiguration { - - @Configuration - @EntityScan("ca.uhn.fhir.jpa.entity") - @EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") - static class FhirJpaDaoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConfigurationProperties("hapi.fhir.jpa") - public DaoConfig fhirDaoConfig() { - DaoConfig fhirDaoConfig = new DaoConfig(); - return fhirDaoConfig; - } - } - - @Configuration - @ConditionalOnBean({ DaoConfig.class, RestfulServer.class }) - @SuppressWarnings("rawtypes") - static class RestfulServerCustomizer implements FhirRestfulServerCustomizer { - - private final BaseJpaSystemProvider systemProviders; - - public RestfulServerCustomizer(ObjectProvider systemProviders) { - this.systemProviders = systemProviders.getIfAvailable(); - } - - @Override - public void customize(RestfulServer server) { - server.setPlainProviders(systemProviders); - } - } - - @Configuration - @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") - @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU3") - static class Dstu3 extends BaseJavaConfigDstu3 { - } - - @Configuration - @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") - @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU2") - static class Dstu2 extends BaseJavaConfigDstu2 { - } - } - - @Configuration - @Conditional(FhirValidationConfiguration.SchemaAvailableCondition.class) - @ConditionalOnProperty(name = "hapi.fhir.validation.enabled", matchIfMissing = true) - static class FhirValidationConfiguration { - - @Bean - @ConditionalOnMissingBean - public RequestValidatingInterceptor requestValidatingInterceptor() { - return new RequestValidatingInterceptor(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = "hapi.fhir.validation.request-only", havingValue = "false") - public ResponseValidatingInterceptor responseValidatingInterceptor() { - return new ResponseValidatingInterceptor(); - } - - static class SchemaAvailableCondition extends ResourceCondition { - - SchemaAvailableCondition() { - super("ValidationSchema", - "hapi.fhir.validation", - "schema-location", - "classpath:/org/hl7/fhir/instance/model/schema", - "classpath:/org/hl7/fhir/dstu2016may/model/schema", - "classpath:/org/hl7/fhir/dstu3/model/schema"); - } - } - } - - @Configuration - @ConditionalOnProperty("hapi.fhir.server.url") - @EnableConfigurationProperties(FhirProperties.class) - static class FhirRestfulClientConfiguration { - - private final FhirProperties properties; - - private final List clientInterceptors; - - public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider> clientInterceptors) { - this.properties = properties; - this.clientInterceptors = clientInterceptors.getIfAvailable(); - } - - @Bean - @ConditionalOnBean(IRestfulClientFactory.class) - public IGenericClient fhirClient(final IRestfulClientFactory clientFactory) { - IGenericClient fhirClient = clientFactory.newGenericClient(this.properties.getServer().getUrl()); - if (!CollectionUtils.isEmpty(this.clientInterceptors)) { - for (IClientInterceptor interceptor : this.clientInterceptors) { - fhirClient.registerInterceptor(interceptor); - } - } - return fhirClient; - } - - @Configuration - @ConditionalOnClass(HttpClient.class) - @ConditionalOnMissingClass("okhttp3.OkHttpClient") - static class Apache { - - private final FhirContext context; - - public Apache(FhirContext context) { - this.context = context; - } - - @Bean - @ConditionalOnMissingBean - @ConfigurationProperties("hapi.fhir.rest.client.apache") - public IRestfulClientFactory fhirRestfulClientFactory() { - ApacheRestfulClientFactory restfulClientFactory = new ApacheRestfulClientFactory(this.context); - return restfulClientFactory; - } - } - - @Configuration - @ConditionalOnClass(OkHttpClient.class) - static class OkHttp { - - private final FhirContext context; - - public OkHttp(FhirContext context) { - this.context = context; - } - - @Bean - @ConditionalOnMissingBean - @ConfigurationProperties("hapi.fhir.rest.client.okhttp") - public IRestfulClientFactory fhirRestfulClientFactory() { - OkHttpRestfulClientFactory restfulClientFactory = new OkHttpRestfulClientFactory(this.context); - return restfulClientFactory; - } - } - } - -} +package ca.uhn.fhir.spring.boot.autoconfigure; + +/*- + * #%L + * hapi-fhir-spring-boot-autoconfigure + * %% + * Copyright (C) 2014 - 2017 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.util.List; + +import javax.servlet.ServletException; +import javax.sql.DataSource; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; +import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; +import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.provider.BaseJpaProvider; +import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider; +import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; +import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory; +import ca.uhn.fhir.rest.client.api.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.IRestfulClientFactory; +import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; +import ca.uhn.fhir.rest.server.IPagingProvider; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; +import okhttp3.OkHttpClient; +import org.apache.http.client.HttpClient; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ResourceCondition; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.util.CollectionUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for HAPI FHIR. + * + * @author Mathieu Ouellet + */ +@Configuration +@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) +@EnableConfigurationProperties(FhirProperties.class) +public class FhirAutoConfiguration { + + private final FhirProperties properties; + + public FhirAutoConfiguration(FhirProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean + public FhirContext fhirContext() { + FhirContext fhirContext = new FhirContext(properties.getVersion()); + return fhirContext; + } + + @Configuration + @ConditionalOnClass(AbstractJaxRsProvider.class) + @EnableConfigurationProperties(FhirProperties.class) + @ConfigurationProperties("hapi.fhir.rest") + @SuppressWarnings("serial") + static class FhirRestfulServerConfiguration extends RestfulServer { + + private final FhirProperties properties; + + private final FhirContext fhirContext; + + private final List resourceProviders; + + private final IPagingProvider pagingProvider; + + private final List interceptors; + + private final List customizers; + + public FhirRestfulServerConfiguration( + FhirProperties properties, + FhirContext fhirContext, + ObjectProvider> resourceProviders, + ObjectProvider pagingProvider, + ObjectProvider> interceptors, + ObjectProvider> customizers) { + this.properties = properties; + this.fhirContext = fhirContext; + this.resourceProviders = resourceProviders.getIfAvailable(); + this.pagingProvider = pagingProvider.getIfAvailable(); + this.interceptors = interceptors.getIfAvailable(); + this.customizers = customizers.getIfAvailable(); + } + + @Bean + public ServletRegistrationBean fhirServerRegistrationBean() { + ServletRegistrationBean registration = new ServletRegistrationBean(this, this.properties.getServer().getPath()); + registration.setLoadOnStartup(1); + return registration; + } + + @Override + protected void initialize() throws ServletException { + super.initialize(); + + setFhirContext(this.fhirContext); + setResourceProviders(this.resourceProviders); + setPagingProvider(this.pagingProvider); + setInterceptors(this.interceptors); + + setServerAddressStrategy(new HardcodedServerAddressStrategy(this.properties.getServer().getPath())); + + customize(); + } + + private void customize() { + if (this.customizers != null) { + AnnotationAwareOrderComparator.sort(this.customizers); + for (FhirRestfulServerCustomizer customizer : this.customizers) { + customizer.customize(this); + } + } + } + } + + @Configuration + @ConditionalOnClass(BaseJpaProvider.class) + @ConditionalOnBean(DataSource.class) + @EnableConfigurationProperties(FhirProperties.class) + static class FhirJpaServerConfiguration { + + @Configuration + @EntityScan("ca.uhn.fhir.jpa.entity") + @EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") + static class FhirJpaDaoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.jpa") + public DaoConfig fhirDaoConfig() { + DaoConfig fhirDaoConfig = new DaoConfig(); + return fhirDaoConfig; + } + } + + @Configuration + @ConditionalOnBean({ DaoConfig.class, RestfulServer.class }) + @SuppressWarnings("rawtypes") + static class RestfulServerCustomizer implements FhirRestfulServerCustomizer { + + private final BaseJpaSystemProvider systemProviders; + + public RestfulServerCustomizer(ObjectProvider systemProviders) { + this.systemProviders = systemProviders.getIfAvailable(); + } + + @Override + public void customize(RestfulServer server) { + server.setPlainProviders(systemProviders); + } + } + + @Configuration + @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") + @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU3") + static class Dstu3 extends BaseJavaConfigDstu3 { + } + + @Configuration + @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") + @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU2") + static class Dstu2 extends BaseJavaConfigDstu2 { + } + } + + @Configuration + @Conditional(FhirValidationConfiguration.SchemaAvailableCondition.class) + @ConditionalOnProperty(name = "hapi.fhir.validation.enabled", matchIfMissing = true) + static class FhirValidationConfiguration { + + @Bean + @ConditionalOnMissingBean + public RequestValidatingInterceptor requestValidatingInterceptor() { + return new RequestValidatingInterceptor(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "hapi.fhir.validation.request-only", havingValue = "false") + public ResponseValidatingInterceptor responseValidatingInterceptor() { + return new ResponseValidatingInterceptor(); + } + + static class SchemaAvailableCondition extends ResourceCondition { + + SchemaAvailableCondition() { + super("ValidationSchema", + "hapi.fhir.validation", + "schema-location", + "classpath:/org/hl7/fhir/instance/model/schema", + "classpath:/org/hl7/fhir/dstu2016may/model/schema", + "classpath:/org/hl7/fhir/dstu3/model/schema"); + } + } + } + + @Configuration + @ConditionalOnProperty("hapi.fhir.server.url") + @EnableConfigurationProperties(FhirProperties.class) + static class FhirRestfulClientConfiguration { + + private final FhirProperties properties; + + private final List clientInterceptors; + + public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider> clientInterceptors) { + this.properties = properties; + this.clientInterceptors = clientInterceptors.getIfAvailable(); + } + + @Bean + @ConditionalOnBean(IRestfulClientFactory.class) + public IGenericClient fhirClient(final IRestfulClientFactory clientFactory) { + IGenericClient fhirClient = clientFactory.newGenericClient(this.properties.getServer().getUrl()); + if (!CollectionUtils.isEmpty(this.clientInterceptors)) { + for (IClientInterceptor interceptor : this.clientInterceptors) { + fhirClient.registerInterceptor(interceptor); + } + } + return fhirClient; + } + + @Configuration + @ConditionalOnClass(HttpClient.class) + @ConditionalOnMissingClass("okhttp3.OkHttpClient") + static class Apache { + + private final FhirContext context; + + public Apache(FhirContext context) { + this.context = context; + } + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.rest.client.apache") + public IRestfulClientFactory fhirRestfulClientFactory() { + ApacheRestfulClientFactory restfulClientFactory = new ApacheRestfulClientFactory(this.context); + return restfulClientFactory; + } + } + + @Configuration + @ConditionalOnClass(OkHttpClient.class) + static class OkHttp { + + private final FhirContext context; + + public OkHttp(FhirContext context) { + this.context = context; + } + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.rest.client.okhttp") + public IRestfulClientFactory fhirRestfulClientFactory() { + OkHttpRestfulClientFactory restfulClientFactory = new OkHttpRestfulClientFactory(this.context); + return restfulClientFactory; + } + } + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java index 5bb8d3cc2f5..f90dce8f2d2 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java @@ -1,85 +1,106 @@ -package ca.uhn.fhir.spring.boot.autoconfigure; - -import ca.uhn.fhir.context.FhirVersionEnum; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "hapi.fhir") -public class FhirProperties { - - private FhirVersionEnum version = FhirVersionEnum.DSTU2; - - private Server server = new Server(); - - private Validation validation = new Validation(); - - public FhirVersionEnum getVersion() { - return version; - } - - public void setVersion(FhirVersionEnum version) { - this.version = version; - } - - public Server getServer() { - return server; - } - - public void setServer(Server server) { - this.server = server; - } - - public Validation getValidation() { - return validation; - } - - public void setValidation(Validation validation) { - this.validation = validation; - } - - public static class Server { - - private String url; - - private String path = "/fhir/*"; - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - } - - public static class Validation { - - private boolean enabled = true; - - private boolean requestOnly = true; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public boolean isRequestOnly() { - return requestOnly; - } - - public void setRequestOnly(boolean requestOnly) { - this.requestOnly = requestOnly; - } - } -} +package ca.uhn.fhir.spring.boot.autoconfigure; + +/*- + * #%L + * hapi-fhir-spring-boot-autoconfigure + * %% + * Copyright (C) 2014 - 2017 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.context.FhirVersionEnum; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "hapi.fhir") +public class FhirProperties { + + private FhirVersionEnum version = FhirVersionEnum.DSTU2; + + private Server server = new Server(); + + private Validation validation = new Validation(); + + public FhirVersionEnum getVersion() { + return version; + } + + public void setVersion(FhirVersionEnum version) { + this.version = version; + } + + public Server getServer() { + return server; + } + + public void setServer(Server server) { + this.server = server; + } + + public Validation getValidation() { + return validation; + } + + public void setValidation(Validation validation) { + this.validation = validation; + } + + public static class Server { + + private String url; + + private String path = "/fhir/*"; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + } + + public static class Validation { + + private boolean enabled = true; + + private boolean requestOnly = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isRequestOnly() { + return requestOnly; + } + + public void setRequestOnly(boolean requestOnly) { + this.requestOnly = requestOnly; + } + } +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java index 3f19d1b58a4..77668189009 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.spring.boot.autoconfigure; +/*- + * #%L + * hapi-fhir-spring-boot-autoconfigure + * %% + * Copyright (C) 2014 - 2017 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.rest.server.RestfulServer; @FunctionalInterface diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java index e553eebc646..dd5568a59ca 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java @@ -1,5 +1,25 @@ package sample.fhir.client; +/*- + * #%L + * hapi-fhir-spring-boot-sample-client-apache + * %% + * Copyright (C) 2014 - 2017 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.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import org.hl7.fhir.dstu3.model.CapabilityStatement; diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java index 6286d41b824..482bbc8b793 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java @@ -1,5 +1,25 @@ package sample.fhir.client; +/*- + * #%L + * hapi-fhir-spring-boot-sample-client-okhttp + * %% + * Copyright (C) 2014 - 2017 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.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import org.hl7.fhir.dstu3.model.CapabilityStatement; diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index 5e04d3b1b4f..43d218f4606 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -1,67 +1,85 @@ - - 4.0.0 - - - ca.uhn.hapi.fhir - hapi-fhir-spring-boot-samples - 3.1.0 - - - hapi-fhir-spring-boot-sample-server-jersey - - jar - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-jersey - - - ca.uhn.hapi.fhir - hapi-fhir-spring-boot-starter - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-jaxrsserver-base - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu3 - ${project.version} - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - \ No newline at end of file + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0 + + + hapi-fhir-spring-boot-sample-server-jersey + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-jersey + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu3 + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + + true + + + + org.basepom.maven + duplicate-finder-maven-plugin + + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java index 76b50ec7101..77ca17aa41c 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java @@ -1,5 +1,25 @@ package sample.fhir.server.jersey; +/*- + * #%L + * hapi-fhir-spring-boot-sample-server-jersey + * %% + * Copyright (C) 2014 - 2017 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.rest.server.interceptor.LoggingInterceptor; import org.springframework.boot.SpringApplication; diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java index 9f12b953a26..9f9f4d5342b 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java @@ -1,5 +1,25 @@ package sample.fhir.server.jersey.provider; +/*- + * #%L + * hapi-fhir-spring-boot-sample-server-jersey + * %% + * Copyright (C) 2014 - 2017 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.util.concurrent.ConcurrentHashMap; import ca.uhn.fhir.context.FhirContext; diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index 673d0f59e54..fcddcffab7c 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -1,27 +1,28 @@ - - 4.0.0 - - - ca.uhn.hapi.fhir - hapi-fhir-spring-boot - 3.1.0 - - - hapi-fhir-spring-boot-starter - - jar - - - - org.springframework.boot - spring-boot-starter - - - ca.uhn.hapi.fhir - hapi-fhir-spring-boot-autoconfigure - ${project.version} - - - - \ No newline at end of file + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-deployable-pom + 3.1.0 + ../../hapi-deployable-pom/pom.xml + + + hapi-fhir-spring-boot-starter + + jar + + + + org.springframework.boot + spring-boot-starter + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-autoconfigure + ${project.version} + + + + diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 6b8f7d477e5..02010713257 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir - hapi-deployable-pom + hapi-fhir 3.1.0 ../pom.xml @@ -17,6 +17,18 @@ hapi-fhir-spring-boot-starter hapi-fhir-spring-boot-samples + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + diff --git a/pom.xml b/pom.xml index d780bda406b..784106b3273 100644 --- a/pom.xml +++ b/pom.xml @@ -219,7 +219,7 @@ SRiviere - Sébastien Rivière + Sébastien Rivière karlmdavis @@ -1174,7 +1174,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.15 + 1.16 org.codehaus.mojo From 040912430261bcd47bdac71f0c161e2d4e0d3ebd Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 23 Nov 2017 15:56:04 -0500 Subject: [PATCH 34/39] Site updates --- .../pom.xml | 152 +++--- src/changes/changes.xml | 7 +- src/site/xdoc/index.xml | 456 +++++++++--------- 3 files changed, 330 insertions(+), 285 deletions(-) diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index 439a5ce9252..92063d065ad 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -1,67 +1,85 @@ - - 4.0.0 - - - ca.uhn.hapi.fhir - hapi-fhir-spring-boot-samples - 3.1.0-SNAPSHOT - - - hapi-fhir-spring-boot-sample-server-jersey - - jar - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-jersey - - - ca.uhn.hapi.fhir - hapi-fhir-spring-boot-starter - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-jaxrsserver-base - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu3 - ${project.version} - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - \ No newline at end of file + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-sample-server-jersey + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-jersey + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu3 + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + + true + + + + org.basepom.maven + duplicate-finder-maven-plugin + + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a8e392dae62..869a2b9463e 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,7 +6,7 @@ HAPI FHIR Changelog - + The version of a few dependencies have been bumped to the latest versions (dependent HAPI modules listed in brackets): @@ -117,7 +117,7 @@ the StAX XMLEvent type as its internal model, and instead simply uses a String. New methods called "parse" and "encode" have been added to HAPI FHIR's XmlUtil class, which can be used to convert - between a String and an XML representatio. This should allow + between a String and an XML representation. This should allow HAPI FHIR to run in environments where StAX is not available, such as Android phones. @@ -198,6 +198,9 @@ Add support for Spring Boot for initializing a number of parts of the library, as well as several examples. + See the + Spring Boot samples]]> + for examples of how this works. Thanks to Mathieu Ouellet for the contribution! diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index ba19775369f..7904a29f779 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -69,6 +69,30 @@

+

+ November 23, 2017 - HAPI FHIR 3.1.0 Released - + The next release of HAPI has now been uploaded to the Maven repos and + GitHub's releases section. +

+

+ This release brings several interesting things: +

+ +
    +
  • Support for Android has been restored, and improved while we're at it! The use of a special "uberjar" with its own classifier is no longer required, hapi-fhir-android works as a normal Gradle dependency in your Android build. See the HAPI FHIR Android Integration Test for an example. +
  • Support for the Cache-Control header has been added for JPA server searches, allowing a client to request that cached results not be used. +
  • A number of bugs were fixed (see the changelog for a full list) +
  • Spring has been upgrade to the 5.0 series. +
+ +

+ Thanks to everyone who contributed to this release! +

+

+ - James Agnew +

+

+

Sep 27, 2017 - HAPI FHIR 3.0.0 Released - The next release of HAPI has now been uploaded to the Maven repos and @@ -173,20 +197,20 @@ the following call would have worked previously:

-Bundle bundle = client.search().forResource(Patient.class)
-	.where(new TokenClientParam("gender").exactly().code("unknown"))
-   .prettyPrint()
-   .execute();
+					Bundle bundle = client.search().forResource(Patient.class)
+					.where(new TokenClientParam("gender").exactly().code("unknown"))
+					.prettyPrint()
+					.execute();
 				
This now needs an explicit returnBundle statement, as follows:
-Bundle bundle = client.search().forResource(Patient.class)
-	.where(new TokenClientParam("gender").exactly().code("unknown"))
-   .prettyPrint()
-   .returnBundle(Bundle.class)
-   .execute();
+					Bundle bundle = client.search().forResource(Patient.class)
+					.where(new TokenClientParam("gender").exactly().code("unknown"))
+					.prettyPrint()
+					.returnBundle(Bundle.class)
+					.execute();
 				

@@ -201,18 +225,19 @@ Bundle bundle = client.search().forResource(Patient.class)



+ - - - + - - - - + + + com.google.code.findbugs + annotations + + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java index 4ca33aa1156..543d2561369 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java @@ -138,19 +138,6 @@ public class ParameterUtil { return b.toString(); } - /** - * Escapes a string according to the rules for parameter escaping specified in the FHIR Specification Escaping - * Section - */ - public static String escapeAndUrlEncode(String theValue) { - if (theValue == null) { - return null; - } - - String escaped = escape(theValue); - return UrlUtil.escape(escaped); - } - /** * Escapes a string according to the rules for parameter escaping specified in the FHIR Specification Escaping * Section diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java index bd43db5bf4a..5349f8bd25c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java @@ -1,16 +1,20 @@ package ca.uhn.fhir.util; -import static org.apache.commons.lang3.StringUtils.defaultIfBlank; -import static org.apache.commons.lang3.StringUtils.isBlank; - -import java.io.UnsupportedEncodingException; -import java.net.*; -import java.util.*; -import java.util.Map.Entry; - import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import com.google.common.escape.Escaper; +import com.google.common.net.PercentEscaper; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.*; +import java.util.Map.Entry; + +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.apache.commons.lang3.StringUtils.isBlank; /* * #%L @@ -35,6 +39,10 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class UrlUtil { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UrlUtil.class); + private static final String URL_FORM_PARAMETER_OTHER_SAFE_CHARS = "-_.*"; + private static final Escaper PARAMETER_ESCAPER = new PercentEscaper(URL_FORM_PARAMETER_OTHER_SAFE_CHARS, false); + + /** * Resolve a relative URL - THIS METHOD WILL NOT FAIL but will log a warning and return theEndpoint if the input is invalid. */ @@ -88,19 +96,24 @@ public class UrlUtil { } /** - * URL encode a value + * URL encode a value according to RFC 3986 + *

+ * This method is intended to be applied to an individual parameter + * name or value. For example, if you are creating the URL + * http://example.com/fhir/Patient?key=føø + * it would be appropriate to pass the string "føø" to this method, + * but not appropriate to pass the entire URL since characters + * such as "/" and "?" would also be escaped. + *

*/ - public static String escape(String theValue) { - if (theValue == null) { + public static String escapeUrlParam(String theUnescaped) { + if (theUnescaped == null) { return null; } - try { - return URLEncoder.encode(theValue, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new Error("UTF-8 not supported on this platform"); - } + return PARAMETER_ESCAPER.escape(theUnescaped); } + public static boolean isAbsolute(String theValue) { String value = theValue.toLowerCase(); return value.startsWith("http://") || value.startsWith("https://"); @@ -147,7 +160,7 @@ public class UrlUtil { } public static void main(String[] args) { - System.out.println(escape("http://snomed.info/sct?fhir_vs=isa/126851005")); + System.out.println(escapeUrlParam("http://snomed.info/sct?fhir_vs=isa/126851005")); } public static Map parseQueryString(String theQueryString) { @@ -156,36 +169,20 @@ public class UrlUtil { return toQueryStringMap(map); } - public static Map parseQueryStrings(String... theQueryString) { - HashMap> map = new HashMap>(); - for (String next : theQueryString) { - parseQueryString(next, map); - } - return toQueryStringMap(map); - } - - private static Map toQueryStringMap(HashMap> map) { - HashMap retVal = new HashMap(); - for (Entry> nextEntry : map.entrySet()) { - retVal.put(nextEntry.getKey(), nextEntry.getValue().toArray(new String[nextEntry.getValue().size()])); - } - return retVal; - } - private static void parseQueryString(String theQueryString, HashMap> map) { String query = theQueryString; if (query.startsWith("?")) { query = query.substring(1); } - - + + StringTokenizer tok = new StringTokenizer(query, "&"); while (tok.hasMoreTokens()) { String nextToken = tok.nextToken(); if (isBlank(nextToken)) { continue; } - + int equalsIndex = nextToken.indexOf('='); String nextValue; String nextKey; @@ -196,21 +193,28 @@ public class UrlUtil { nextKey = nextToken.substring(0, equalsIndex); nextValue = nextToken.substring(equalsIndex + 1); } - + nextKey = unescape(nextKey); nextValue = unescape(nextValue); - + List list = map.get(nextKey); if (list == null) { - list = new ArrayList(); + list = new ArrayList<>(); map.put(nextKey, list); } list.add(nextValue); } } - //@formatter:off - /** + public static Map parseQueryStrings(String... theQueryString) { + HashMap> map = new HashMap>(); + for (String next : theQueryString) { + parseQueryString(next, map); + } + return toQueryStringMap(map); + } + + /** * Parse a URL in one of the following forms: *
    *
  • [Resource Type]?[Search Params] @@ -278,6 +282,16 @@ public class UrlUtil { } + //@formatter:off + + private static Map toQueryStringMap(HashMap> map) { + HashMap retVal = new HashMap(); + for (Entry> nextEntry : map.entrySet()) { + retVal.put(nextEntry.getKey(), nextEntry.getValue().toArray(new String[nextEntry.getValue().size()])); + } + return retVal; + } + public static String unescape(String theString) { if (theString == null) { return null; @@ -305,30 +319,30 @@ public class UrlUtil { return myParams; } - public String getResourceId() { - return myResourceId; - } - - public String getResourceType() { - return myResourceType; - } - - public String getVersionId() { - return myVersionId; - } - public void setParams(String theParams) { myParams = theParams; } + public String getResourceId() { + return myResourceId; + } + public void setResourceId(String theResourceId) { myResourceId = theResourceId; } + public String getResourceType() { + return myResourceType; + } + public void setResourceType(String theResourceType) { myResourceType = theResourceType; } + public String getVersionId() { + return myVersionId; + } + public void setVersionId(String theVersionId) { myVersionId = theVersionId; } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/UrlUtilTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/UrlUtilTest.java index d55b7a9a1cc..e949a6275dd 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/UrlUtilTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/UrlUtilTest.java @@ -1,20 +1,42 @@ package ca.uhn.fhir.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - import org.junit.Test; +import static org.junit.Assert.*; + public class UrlUtilTest { + @Test + public void testConstructAbsoluteUrl() { + assertEquals("http://foo/bar/baz", UrlUtil.constructAbsoluteUrl(null, "http://foo/bar/baz")); + assertEquals("http://foo/bar/baz", UrlUtil.constructAbsoluteUrl("http://foo/bar/", "baz")); + assertEquals("http://foo/bar/baz/", UrlUtil.constructAbsoluteUrl("http://foo/bar/", "baz/")); + assertEquals("http://foo/bar/baz/", UrlUtil.constructAbsoluteUrl("http://foo/bar/", "./baz/")); + + assertEquals("http://foo/baz/", UrlUtil.constructAbsoluteUrl("http://foo/bar/", "../baz/")); + assertEquals("http://foo/baz/", UrlUtil.constructAbsoluteUrl("http://foo/bar/", "/baz/")); + } + + @Test + public void testConstructRelativeUrl() { + assertEquals("http://foo/bar/baz", UrlUtil.constructRelativeUrl("http://boo/far/faz", "http://foo/bar/baz")); + assertEquals("http://foo/bar/baz", UrlUtil.constructRelativeUrl("http://foo/far/faz", "http://foo/bar/baz")); + assertEquals("baz", UrlUtil.constructRelativeUrl("http://foo/bar/boo", "http://foo/bar/baz")); + } + + @Test + public void testEscape() { + assertEquals("A%20B", UrlUtil.escapeUrlParam("A B")); + assertEquals("A%2BB", UrlUtil.escapeUrlParam("A+B")); + } + @Test public void testIsValid() { assertTrue(UrlUtil.isValid("http://foo")); assertTrue(UrlUtil.isValid("https://foo")); assertTrue(UrlUtil.isValid("HTTP://Foo")); assertTrue(UrlUtil.isValid("HTTPS://Foo")); - + assertFalse(UrlUtil.isValid("file://foo")); assertFalse(UrlUtil.isValid("://foo")); assertFalse(UrlUtil.isValid("http:/ss")); @@ -24,7 +46,7 @@ public class UrlUtilTest { assertFalse(UrlUtil.isValid("")); assertFalse(UrlUtil.isValid(null)); } - + @Test public void testParseUrl() { assertEquals("ConceptMap", UrlUtil.parseUrl("http://hl7.org/fhir/ConceptMap/ussgfht-loincde").getResourceType()); @@ -36,25 +58,5 @@ public class UrlUtilTest { assertEquals("a=b", UrlUtil.parseUrl("ConceptMap/ussgfht-loincde?a=b").getParams()); } - - @Test - public void testConstructAbsoluteUrl() { - assertEquals("http://foo/bar/baz", UrlUtil.constructAbsoluteUrl(null, "http://foo/bar/baz")); - assertEquals("http://foo/bar/baz", UrlUtil.constructAbsoluteUrl("http://foo/bar/","baz")); - assertEquals("http://foo/bar/baz/", UrlUtil.constructAbsoluteUrl("http://foo/bar/","baz/")); - assertEquals("http://foo/bar/baz/", UrlUtil.constructAbsoluteUrl("http://foo/bar/","./baz/")); - assertEquals("http://foo/baz/", UrlUtil.constructAbsoluteUrl("http://foo/bar/","../baz/")); - assertEquals("http://foo/baz/", UrlUtil.constructAbsoluteUrl("http://foo/bar/","/baz/")); - } - - - - @Test - public void testConstructRelativeUrl() { - assertEquals("http://foo/bar/baz", UrlUtil.constructRelativeUrl("http://boo/far/faz", "http://foo/bar/baz")); - assertEquals("http://foo/bar/baz", UrlUtil.constructRelativeUrl("http://foo/far/faz", "http://foo/bar/baz")); - assertEquals("baz", UrlUtil.constructRelativeUrl("http://foo/bar/boo", "http://foo/bar/baz")); - } - } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseHttpClientInvocation.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseHttpClientInvocation.java index b9fa14db159..1aa48b1286a 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseHttpClientInvocation.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseHttpClientInvocation.java @@ -20,13 +20,6 @@ package ca.uhn.fhir.rest.client.impl; * #L% */ -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; @@ -34,6 +27,12 @@ import ca.uhn.fhir.rest.client.api.Header; import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IRestfulClientFactory; +import ca.uhn.fhir.util.UrlUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; public abstract class BaseHttpClientInvocation { @@ -115,13 +114,9 @@ public abstract class BaseHttpClientInvocation { } else { theUrlBuilder.append('&'); } - try { - theUrlBuilder.append(URLEncoder.encode(next.getKey(), "UTF-8")); - theUrlBuilder.append('='); - theUrlBuilder.append(URLEncoder.encode(nextValue, "UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new Error("UTF-8 not supported - This should not happen"); - } + theUrlBuilder.append(UrlUtil.escapeUrlParam(next.getKey())); + theUrlBuilder.append('='); + theUrlBuilder.append(UrlUtil.escapeUrlParam(nextValue)); } } } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java index 60687a5f7b9..1432d8c5a00 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java @@ -354,7 +354,7 @@ public class GenericClient extends BaseClient implements IGenericClient { case '?': case '$': case ':': - b.append(UrlUtil.escape(Character.toString(nextChar))); + b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar))); break; default: b.append(nextChar); diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpGetClientInvocation.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpGetClientInvocation.java index 2f8b15e609a..21287d59ba8 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpGetClientInvocation.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpGetClientInvocation.java @@ -20,21 +20,18 @@ package ca.uhn.fhir.rest.client.method; * #L% */ -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.apache.commons.lang3.StringUtils; - -import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation; +import ca.uhn.fhir.util.UrlUtil; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; /** * @author James Agnew @@ -51,51 +48,40 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation { myUrlPath = StringUtils.join(theUrlFragments, '/'); } - public HttpGetClientInvocation(FhirContext theContext, Map> theParameters, List theUrlFragments) { - super(theContext); - myParameters = theParameters; - myUrlPath = StringUtils.join(theUrlFragments, '/'); - } - public HttpGetClientInvocation(FhirContext theContext, String theUrlPath) { super(theContext); - myParameters = new HashMap>(); + myParameters = new HashMap<>(); myUrlPath = theUrlPath; } - public HttpGetClientInvocation(FhirContext theContext, String... theUrlFragments) { - super(theContext); - myParameters = new HashMap>(); - myUrlPath = StringUtils.join(theUrlFragments, '/'); - } + private boolean addQueryParameter(StringBuilder b, boolean first, String nextKey, String nextValue) { + boolean retVal = first; + if (retVal) { + b.append('?'); + retVal = false; + } else { + b.append('&'); + } + b.append(UrlUtil.escapeUrlParam(nextKey)); + b.append('='); + b.append(UrlUtil.escapeUrlParam(nextValue)); - public HttpGetClientInvocation(FhirContext theContext, List theUrlFragments) { - super(theContext); - myParameters = new HashMap>(); - myUrlPath = StringUtils.join(theUrlFragments, '/'); - } - - public Map> getParameters() { - return myParameters; - } - - public String getUrlPath() { - return myUrlPath; + return retVal; } @Override public IHttpRequest asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { StringBuilder b = new StringBuilder(); - + if (!myUrlPath.contains("://")) { - b.append(theUrlBase); - if (!theUrlBase.endsWith("/") && !myUrlPath.startsWith("/")) { - b.append('/'); - } - } - b.append(myUrlPath); - + b.append(theUrlBase); + if (!theUrlBase.endsWith("/") && !myUrlPath.startsWith("/")) { + b.append('/'); + } + } + b.append(myUrlPath); + boolean first = b.indexOf("?") == -1; for (Entry> next : myParameters.entrySet()) { if (next.getValue() == null || next.getValue().isEmpty()) { @@ -112,22 +98,12 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation { return super.createHttpRequest(b.toString(), theEncoding, RequestTypeEnum.GET); } - private boolean addQueryParameter(StringBuilder b, boolean first, String nextKey, String nextValue) { - boolean retVal = first; - if (retVal) { - b.append('?'); - retVal = false; - } else { - b.append('&'); - } - try { - b.append(URLEncoder.encode(nextKey, "UTF-8")); - b.append('='); - b.append(URLEncoder.encode(nextValue, "UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new ConfigurationException("Could not find UTF-8 encoding. This shouldn't happen.", e); - } - return retVal; + public Map> getParameters() { + return myParameters; + } + + public String getUrlPath() { + return myUrlPath; } } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java index ea1ad89d64a..5be18f1cf70 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java @@ -209,9 +209,9 @@ public class MethodUtil { for (String nextValue : nextEntry.getValue()) { b.append(haveQuestionMark ? '&' : '?'); haveQuestionMark = true; - b.append(UrlUtil.escape(nextEntry.getKey())); + b.append(UrlUtil.escapeUrlParam(nextEntry.getKey())); b.append('='); - b.append(UrlUtil.escape(nextValue)); + b.append(UrlUtil.escapeUrlParam(nextValue)); } } return b; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 772638bb774..8707ab64240 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -35,7 +35,6 @@ import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; @@ -229,14 +228,14 @@ public abstract class BaseHapiFhirDao implements IDao { ArrayList nextChoicesList = new ArrayList<>(); partsChoices.add(nextChoicesList); - String key = UrlUtil.escape(nextCompositeOf.getName()); + String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName()); if (paramsListForCompositePart != null) { for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) { if (nextParam.getParamName().equals(nextCompositeOf.getName())) { IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType(); String value = nextParamAsClientParam.getValueAsQueryToken(getContext()); if (isNotBlank(value)) { - value = UrlUtil.escape(value); + value = UrlUtil.escapeUrlParam(value); nextChoicesList.add(key + "=" + value); } } @@ -246,7 +245,7 @@ public abstract class BaseHapiFhirDao implements IDao { for (ResourceLink nextLink : linksForCompositePart) { String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue(); if (isNotBlank(value)) { - value = UrlUtil.escape(value); + value = UrlUtil.escapeUrlParam(value); nextChoicesList.add(key + "=" + value); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index f791b404272..c4568fa629e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -60,7 +60,6 @@ import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.query.Query; import org.hl7.fhir.dstu3.model.BaseResource; -import org.hl7.fhir.dstu3.model.Reference; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -175,7 +174,7 @@ public class SearchBuilder implements ISearchBuilder { if (valueBuilder.length() > 0) { valueBuilder.append(','); } - valueBuilder.append(UrlUtil.escape(next.getValueAsQueryToken(myContext))); + valueBuilder.append(UrlUtil.escapeUrlParam(next.getValueAsQueryToken(myContext))); targetResourceType = next.getTargetResourceType(); owningParameter = next.getOwningFieldName(); parameterName = next.getParameterName(); @@ -185,7 +184,7 @@ public class SearchBuilder implements ISearchBuilder { continue; } - String matchUrl = targetResourceType + '?' + UrlUtil.escape(parameterName) + '=' + valueBuilder.toString(); + String matchUrl = targetResourceType + '?' + UrlUtil.escapeUrlParam(parameterName) + '=' + valueBuilder.toString(); RuntimeResourceDefinition targetResourceDefinition; try { targetResourceDefinition = myContext.getResourceDefinition(targetResourceType); @@ -1272,13 +1271,13 @@ public class SearchBuilder implements ISearchBuilder { List> params = new ArrayList<>(); for (Entry>> nextParamNameToValues : theParams.entrySet()) { String nextParamName = nextParamNameToValues.getKey(); - nextParamName = UrlUtil.escape(nextParamName); + nextParamName = UrlUtil.escapeUrlParam(nextParamName); for (List nextAnd : nextParamNameToValues.getValue()) { ArrayList nextValueList = new ArrayList<>(); params.add(nextValueList); for (IQueryParameterType nextOr : nextAnd) { String nextOrValue = nextOr.getValueAsQueryToken(myContext); - nextOrValue = UrlUtil.escape(nextOrValue); + nextOrValue = UrlUtil.escapeUrlParam(nextOrValue); nextValueList.add(nextParamName + "=" + nextOrValue); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java index 864f4531981..0b14c5d8e5f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java @@ -1,6 +1,5 @@ package ca.uhn.fhir.jpa.dao; -import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; /* @@ -24,7 +23,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; */ import java.util.*; -import ca.uhn.fhir.rest.param.ReferenceParam; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -138,9 +136,9 @@ public class SearchParameterMap extends LinkedHashMap nextValuesAnd : nextValuesAndsOut) { addUrlParamSeparator(b); IQueryParameterType firstValue = nextValuesAnd.get(0); - b.append(UrlUtil.escape(nextKey)); + b.append(UrlUtil.escapeUrlParam(nextKey)); if (firstValue.getMissing() != null) { b.append(Constants.PARAMQUALIFIER_MISSING); @@ -340,7 +338,7 @@ public class SearchParameterMap extends LinkedHashMap { String nextReplacementIdPart = nextReplacementId.getValueAsString(); if (nextTemporaryId.isUrn() && nextTemporaryIdPart.length() > IdType.URN_PREFIX.length()) { matchUrl = matchUrl.replace(nextTemporaryIdPart, nextReplacementIdPart); - matchUrl = matchUrl.replace(UrlUtil.escape(nextTemporaryIdPart), nextReplacementIdPart); + matchUrl = matchUrl.replace(UrlUtil.escapeUrlParam(nextTemporaryIdPart), nextReplacementIdPart); } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java index 9e9ab48f2c6..95be502404a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java @@ -667,7 +667,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao { String nextReplacementIdPart = nextReplacementId.getValueAsString(); if (nextTemporaryId.isUrn() && nextTemporaryIdPart.length() > IdType.URN_PREFIX.length()) { matchUrl = matchUrl.replace(nextTemporaryIdPart, nextReplacementIdPart); - matchUrl = matchUrl.replace(UrlUtil.escape(nextTemporaryIdPart), nextReplacementIdPart); + matchUrl = matchUrl.replace(UrlUtil.escapeUrlParam(nextTemporaryIdPart), nextReplacementIdPart); } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java index 2290cb34384..e94a2a71700 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java @@ -321,7 +321,7 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc implements I cs.setUrl(theSystem); cs.setContent(CodeSystemContentMode.NOTPRESENT); - DaoMethodOutcome createOutcome = myCodeSystemResourceDao.create(cs, "CodeSystem?url=" + UrlUtil.escape(theSystem), theRequestDetails); + DaoMethodOutcome createOutcome = myCodeSystemResourceDao.create(cs, "CodeSystem?url=" + UrlUtil.escapeUrlParam(theSystem), theRequestDetails); IIdType csId = createOutcome.getId().toUnqualifiedVersionless(); if (createOutcome.getCreated() != Boolean.TRUE) { CodeSystem existing = myCodeSystemResourceDao.read(csId, theRequestDetails); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java index 66336118352..8623d04e4f3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java @@ -325,7 +325,7 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IVal cs.setUrl(theSystem); cs.setContent(CodeSystemContentMode.NOTPRESENT); - DaoMethodOutcome createOutcome = myCodeSystemResourceDao.create(cs, "CodeSystem?url=" + UrlUtil.escape(theSystem), theRequestDetails); + DaoMethodOutcome createOutcome = myCodeSystemResourceDao.create(cs, "CodeSystem?url=" + UrlUtil.escapeUrlParam(theSystem), theRequestDetails); IIdType csId = createOutcome.getId().toUnqualifiedVersionless(); if (createOutcome.getCreated() != Boolean.TRUE) { CodeSystem existing = myCodeSystemResourceDao.read(csId, theRequestDetails); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java index fa04bb5b350..ba49a17ffda 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java @@ -30,7 +30,6 @@ import ca.uhn.fhir.model.dstu2.resource.*; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.valueset.*; import ca.uhn.fhir.model.primitive.*; -import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.client.api.IGenericClient; @@ -528,7 +527,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { pt.addName().addFamily("FOO"); resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); - HttpPut put = new HttpPut(ourServerBase + "/Patient?identifier=" + ("http://general-hospital.co.uk/Identifiers|09832345234543876876".replace("|", UrlUtil.escape("|")))); + HttpPut put = new HttpPut(ourServerBase + "/Patient?identifier=" + ("http://general-hospital.co.uk/Identifiers|09832345234543876876".replace("|", UrlUtil.escapeUrlParam("|")))); put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); IdDt id2; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index 47baa956052..9dea55a78ed 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -1639,7 +1639,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { myObservationDao.create(obs, mySrd); } - String uri = ourServerBase + "/Patient?_has:Observation:subject:identifier=" + UrlUtil.escape("urn:system|FOO"); + String uri = ourServerBase + "/Patient?_has:Observation:subject:identifier=" + UrlUtil.escapeUrlParam("urn:system|FOO"); List ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); assertThat(ids, contains(pid0.getValue())); } @@ -2347,7 +2347,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { myPatientDao.create(p, mySrd); } - String uri = ourServerBase + "/Patient?name=" + URLEncoder.encode("Jernelöv", "UTF-8") + "&_count=5&_pretty=true"; + String uri = ourServerBase + "/Patient?name=" + UrlUtil.escapeUrlParam("Jernelöv") + "&_count=5&_pretty=true"; ourLog.info("URI: {}", uri); HttpGet get = new HttpGet(uri); CloseableHttpResponse resp = ourHttpClient.execute(get); @@ -3792,7 +3792,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { pt.addName().setFamily("FOO"); resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); - HttpPut put = new HttpPut(ourServerBase + "/Patient?identifier=" + ("http://general-hospital.co.uk/Identifiers|09832345234543876876".replace("|", UrlUtil.escape("|")))); + HttpPut put = new HttpPut(ourServerBase + "/Patient?identifier=" + ("http://general-hospital.co.uk/Identifiers|09832345234543876876".replace("|", UrlUtil.escapeUrlParam("|")))); put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); IdType id2; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/GraphQLProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/GraphQLProviderR4Test.java index 286c6d9ce0b..7842f99a75d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/GraphQLProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/GraphQLProviderR4Test.java @@ -25,7 +25,7 @@ public class GraphQLProviderR4Test extends BaseResourceProviderR4Test { initTestPatients(); String query = "{name{family,given}}"; - HttpGet httpGet = new HttpGet(ourServerBase + "/Patient/" + myPatientId0.getIdPart() + "/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet(ourServerBase + "/Patient/" + myPatientId0.getIdPart() + "/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse response = ourHttpClient.execute(httpGet); try { @@ -50,7 +50,7 @@ public class GraphQLProviderR4Test extends BaseResourceProviderR4Test { initTestPatients(); String query = "{PatientList(given:\"given\"){name{family,given}}}"; - HttpGet httpGet = new HttpGet(ourServerBase + "/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet(ourServerBase + "/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse response = ourHttpClient.execute(httpGet); try { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index eddaab5fbd0..1ee7135ecfc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -61,7 +61,6 @@ import java.math.BigDecimal; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; -import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; @@ -1647,7 +1646,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myObservationDao.create(obs, mySrd); } - String uri = ourServerBase + "/Patient?_has:Observation:subject:identifier=" + UrlUtil.escape("urn:system|FOO"); + String uri = ourServerBase + "/Patient?_has:Observation:subject:identifier=" + UrlUtil.escapeUrlParam("urn:system|FOO"); List ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); assertThat(ids, contains(pid0.getValue())); } @@ -2355,7 +2354,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myPatientDao.create(p, mySrd); } - String uri = ourServerBase + "/Patient?name=" + URLEncoder.encode("Jernelöv", "UTF-8") + "&_count=5&_pretty=true"; + String uri = ourServerBase + "/Patient?name=" + UrlUtil.escapeUrlParam("Jernelöv") + "&_count=5&_pretty=true"; ourLog.info("URI: {}", uri); HttpGet get = new HttpGet(uri); CloseableHttpResponse resp = ourHttpClient.execute(get); @@ -3938,7 +3937,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { pt.addName().setFamily("FOO"); resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); - HttpPut put = new HttpPut(ourServerBase + "/Patient?identifier=" + ("http://general-hospital.co.uk/Identifiers|09832345234543876876".replace("|", UrlUtil.escape("|")))); + HttpPut put = new HttpPut(ourServerBase + "/Patient?identifier=" + ("http://general-hospital.co.uk/Identifiers|09832345234543876876".replace("|", UrlUtil.escapeUrlParam("|")))); put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); IdType id2; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/interceptor/AnalyticsInterceptor.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/interceptor/AnalyticsInterceptor.java index b659903135a..64baf5158d4 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/interceptor/AnalyticsInterceptor.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/interceptor/AnalyticsInterceptor.java @@ -80,13 +80,13 @@ public class AnalyticsInterceptor extends InterceptorAdapter { b.append("&tid=").append(myAnalyticsTid); b.append("&t=event"); - b.append("&an=").append(UrlUtil.escape(myHostname)).append('+').append(UrlUtil.escape(next.getApplicationName())); + b.append("&an=").append(UrlUtil.escapeUrlParam(myHostname)).append('+').append(UrlUtil.escapeUrlParam(next.getApplicationName())); b.append("&ec=").append(next.getResourceName()); b.append("&ea=").append(next.getRestOperation()); b.append("&cid=").append(next.getClientId()); - b.append("&uip=").append(UrlUtil.escape(next.getSourceIp())); - b.append("&ua=").append(UrlUtil.escape(next.getUserAgent())); + b.append("&uip=").append(UrlUtil.escapeUrlParam(next.getSourceIp())); + b.append("&ua=").append(UrlUtil.escapeUrlParam(next.getUserAgent())); b.append("\n"); } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index 0dfb36051e9..cbe3dd0fa84 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -19,34 +19,38 @@ package ca.uhn.fhir.rest.server; * limitations under the License. * #L% */ -import static org.apache.commons.lang3.StringUtils.*; - -import java.io.*; -import java.net.URLEncoder; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.servlet.http.HttpServletRequest; - -import ca.uhn.fhir.util.BinaryUtil; -import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.PreferReturnEnum; +import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.api.server.IRestfulResponse; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.method.ElementsParameter; import ca.uhn.fhir.rest.server.method.SummaryEnumParameter; +import ca.uhn.fhir.util.BinaryUtil; import ca.uhn.fhir.util.DateUtils; import ca.uhn.fhir.util.UrlUtil; +import org.hl7.fhir.instance.model.api.*; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.Writer; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.apache.commons.lang3.StringUtils.*; public class RestfulServerUtils { static final Pattern ACCEPT_HEADER_PATTERN = Pattern.compile("\\s*([a-zA-Z0-9+.*/-]+)\\s*(;\\s*([a-zA-Z]+)\\s*=\\s*([a-zA-Z0-9.]+)\\s*)?(,?)"); @@ -127,74 +131,70 @@ public class RestfulServerUtils { } public static String createPagingLink(Set theIncludes, String theServerBase, String theSearchId, int theOffset, int theCount, Map theRequestParameters, boolean thePrettyPrint, - BundleTypeEnum theBundleType) { - try { - StringBuilder b = new StringBuilder(); - b.append(theServerBase); - b.append('?'); - b.append(Constants.PARAM_PAGINGACTION); - b.append('='); - b.append(URLEncoder.encode(theSearchId, "UTF-8")); + BundleTypeEnum theBundleType) { + StringBuilder b = new StringBuilder(); + b.append(theServerBase); + b.append('?'); + b.append(Constants.PARAM_PAGINGACTION); + b.append('='); + b.append(UrlUtil.escapeUrlParam(theSearchId)); + b.append('&'); + b.append(Constants.PARAM_PAGINGOFFSET); + b.append('='); + b.append(theOffset); + b.append('&'); + b.append(Constants.PARAM_COUNT); + b.append('='); + b.append(theCount); + String[] strings = theRequestParameters.get(Constants.PARAM_FORMAT); + if (strings != null && strings.length > 0) { b.append('&'); - b.append(Constants.PARAM_PAGINGOFFSET); + b.append(Constants.PARAM_FORMAT); b.append('='); - b.append(theOffset); - b.append('&'); - b.append(Constants.PARAM_COUNT); - b.append('='); - b.append(theCount); - String[] strings = theRequestParameters.get(Constants.PARAM_FORMAT); - if (strings != null && strings.length > 0) { - b.append('&'); - b.append(Constants.PARAM_FORMAT); - b.append('='); - String format = strings[0]; - format = replace(format, " ", "+"); - b.append(UrlUtil.escape(format)); - } - if (thePrettyPrint) { - b.append('&'); - b.append(Constants.PARAM_PRETTY); - b.append('='); - b.append(Constants.PARAM_PRETTY_VALUE_TRUE); - } - - if (theIncludes != null) { - for (Include nextInclude : theIncludes) { - if (isNotBlank(nextInclude.getValue())) { - b.append('&'); - b.append(Constants.PARAM_INCLUDE); - b.append('='); - b.append(URLEncoder.encode(nextInclude.getValue(), "UTF-8")); - } - } - } - - if (theBundleType != null) { - b.append('&'); - b.append(Constants.PARAM_BUNDLETYPE); - b.append('='); - b.append(theBundleType.getCode()); - } - - String paramName = Constants.PARAM_ELEMENTS; - String[] params = theRequestParameters.get(paramName); - if (params != null) { - for (String nextValue : params) { - if (isNotBlank(nextValue)) { - b.append('&'); - b.append(paramName); - b.append('='); - b.append(UrlUtil.escape(nextValue)); - } - } - } - - return b.toString(); - } catch (UnsupportedEncodingException e) { - throw new Error("UTF-8 not supported", e);// should not happen + String format = strings[0]; + format = replace(format, " ", "+"); + b.append(UrlUtil.escapeUrlParam(format)); } + if (thePrettyPrint) { + b.append('&'); + b.append(Constants.PARAM_PRETTY); + b.append('='); + b.append(Constants.PARAM_PRETTY_VALUE_TRUE); + } + + if (theIncludes != null) { + for (Include nextInclude : theIncludes) { + if (isNotBlank(nextInclude.getValue())) { + b.append('&'); + b.append(Constants.PARAM_INCLUDE); + b.append('='); + b.append(UrlUtil.escapeUrlParam(nextInclude.getValue())); + } + } + } + + if (theBundleType != null) { + b.append('&'); + b.append(Constants.PARAM_BUNDLETYPE); + b.append('='); + b.append(theBundleType.getCode()); + } + + String paramName = Constants.PARAM_ELEMENTS; + String[] params = theRequestParameters.get(paramName); + if (params != null) { + for (String nextValue : params) { + if (isNotBlank(nextValue)) { + b.append('&'); + b.append(paramName); + b.append('='); + b.append(UrlUtil.escapeUrlParam(nextValue)); + } + } + } + + return b.toString(); } /** @@ -392,15 +392,15 @@ public class RestfulServerUtils { try { NarrativeModeEnum narrativeMode = NarrativeModeEnum.valueOfCaseInsensitive(narrative[0]); switch (narrativeMode) { - case NORMAL: - retVal = Collections.singleton(SummaryEnum.FALSE); - break; - case ONLY: - retVal = Collections.singleton(SummaryEnum.TEXT); - break; - case SUPPRESS: - retVal = Collections.singleton(SummaryEnum.DATA); - break; + case NORMAL: + retVal = Collections.singleton(SummaryEnum.FALSE); + break; + case ONLY: + retVal = Collections.singleton(SummaryEnum.TEXT); + break; + case SUPPRESS: + retVal = Collections.singleton(SummaryEnum.DATA); + break; } } catch (IllegalArgumentException e) { ourLog.debug("Invalid {} parameter: {}", Constants.PARAM_NARRATIVE, narrative[0]); @@ -461,13 +461,13 @@ public class RestfulServerUtils { EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails).getEncoding(); IParser parser; switch (responseEncoding) { - case JSON: - parser = theContext.newJsonParser(); - break; - case XML: - default: - parser = theContext.newXmlParser(); - break; + case JSON: + parser = theContext.newJsonParser(); + break; + case XML: + default: + parser = theContext.newXmlParser(); + break; } configureResponseParser(theRequestDetails, parser); @@ -581,13 +581,13 @@ public class RestfulServerUtils { } public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set theSummaryMode, int stausCode, boolean theAddContentLocationHeader, - boolean respondGzip, RequestDetails theRequestDetails) throws IOException { + boolean respondGzip, RequestDetails theRequestDetails) throws IOException { return streamResponseAsResource(theServer, theResource, theSummaryMode, stausCode, null, theAddContentLocationHeader, respondGzip, theRequestDetails, null, null); } public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set theSummaryMode, int theStausCode, String theStatusMessage, - boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType theOperationResourceLastUpdated) - throws IOException { + boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType theOperationResourceLastUpdated) + throws IOException { IRestfulResponse response = theRequestDetails.getResponse(); // Determine response encoding @@ -706,7 +706,7 @@ public class RestfulServerUtils { try { return Integer.parseInt(retVal[0]); } catch (NumberFormatException e) { - ourLog.debug("Failed to parse {} value '{}': {}", new Object[] { theParamName, retVal[0], e }); + ourLog.debug("Failed to parse {} value '{}': {}", new Object[]{theParamName, retVal[0], e}); return null; } } @@ -752,7 +752,7 @@ public class RestfulServerUtils { if (theContentType.equals(EncodingEnum.JSON_PLAIN_STRING) || theContentType.equals(EncodingEnum.XML_PLAIN_STRING)) { FhirVersionEnum ctxtEnum = theCtx.getVersion().getVersion(); myNonLegacy = ctxtEnum.isNewerThan(FhirVersionEnum.DSTU3) - || (ctxtEnum.isEquivalentTo(FhirVersionEnum.DSTU3) && !"1.4.0".equals(theCtx.getVersion().getVersion().getFhirVersionString())); + || (ctxtEnum.isEquivalentTo(FhirVersionEnum.DSTU3) && !"1.4.0".equals(theCtx.getVersion().getVersion().getFhirVersionString())); } else { myNonLegacy = EncodingEnum.isNonLegacy(theContentType); } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java index 729ab9d9cb2..83e732e94b5 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java @@ -25,7 +25,6 @@ import java.io.IOException; */ import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.Date; import java.util.Map.Entry; @@ -33,6 +32,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import ca.uhn.fhir.util.UrlUtil; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.text.StrLookup; @@ -277,13 +277,9 @@ public class LoggingInterceptor extends InterceptorAdapter { } else { b.append('&'); } - try { - b.append(URLEncoder.encode(next.getKey(), "UTF-8")); - b.append('='); - b.append(URLEncoder.encode(nextValue, "UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new ca.uhn.fhir.context.ConfigurationException("UTF-8 not supported", e); - } + b.append(UrlUtil.escapeUrlParam(next.getKey())); + b.append('='); + b.append(UrlUtil.escapeUrlParam(nextValue)); } } return b.toString(); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java index d3955dbc71b..91278e5f874 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java @@ -92,9 +92,9 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { } else { rawB.append('&'); } - rawB.append(UrlUtil.escape(next)); + rawB.append(UrlUtil.escapeUrlParam(next)); rawB.append('='); - rawB.append(UrlUtil.escape(nextValue)); + rawB.append(UrlUtil.escapeUrlParam(nextValue)); } } if (rawB.length() == 0) { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java index 60820b85046..88b41a0eab1 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java @@ -186,9 +186,9 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi } else { b.append('&'); } - b.append(UrlUtil.escape(nextParamName)); + b.append(UrlUtil.escapeUrlParam(nextParamName)); b.append('='); - b.append(UrlUtil.escape(nextParamValue)); + b.append(UrlUtil.escapeUrlParam(nextParamValue)); } } } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/CompositeParameterTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/CompositeParameterTest.java index 74f4ae7abbe..3d46074c6cb 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/CompositeParameterTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/CompositeParameterTest.java @@ -1,12 +1,15 @@ package ca.uhn.fhir.rest.server; -import static org.junit.Assert.*; - -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.BundleEntry; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Observation; +import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; @@ -20,20 +23,11 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.BundleEntry; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu.resource.Observation; -import ca.uhn.fhir.model.primitive.DateTimeDt; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.param.CompositeAndListParam; -import ca.uhn.fhir.rest.param.CompositeOrListParam; -import ca.uhn.fhir.rest.param.CompositeParam; -import ca.uhn.fhir.rest.param.DateParam; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.util.PortUtil; -import ca.uhn.fhir.util.TestUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; /** * Created by dsotnikov on 2/25/2014. @@ -48,7 +42,7 @@ public class CompositeParameterTest { @Test public void testSearchWithDateValue() throws Exception { { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?" + Observation.SP_NAME_VALUE_DATE + "=" + URLEncoder.encode("foo\\$bar$2001-01-01", "UTF-8")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?" + Observation.SP_NAME_VALUE_DATE + "=" + UrlUtil.escape("foo\\$bar$2001-01-01")); HttpResponse status = ourClient.execute(httpGet); String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); @@ -66,7 +60,7 @@ public class CompositeParameterTest { @Test public void testSearchWithMultipleValue() throws Exception { { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?" + Observation.SP_NAME_VALUE_STRING + "=" + URLEncoder.encode("l1$r1,l2$r2", "UTF-8") + "&" + Observation.SP_NAME_VALUE_STRING + "=" + URLEncoder.encode("l3$r3,l4$r4", "UTF-8")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?" + Observation.SP_NAME_VALUE_STRING + "=" + UrlUtil.escape("l1$r1,l2$r2") + "&" + Observation.SP_NAME_VALUE_STRING + "=" + UrlUtil.escape("l3$r3,l4$r4")); HttpResponse status = ourClient.execute(httpGet); String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DefaultEncodingTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DefaultEncodingTest.java index bf1de077702..b104010e93e 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DefaultEncodingTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DefaultEncodingTest.java @@ -1,15 +1,14 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.Assert.*; - -import java.net.URLEncoder; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; -import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; @@ -22,20 +21,11 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import com.google.common.net.UrlEscapers; +import java.util.concurrent.TimeUnit; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu.composite.IdentifierDt; -import ca.uhn.fhir.model.dstu.resource.BaseResource; -import ca.uhn.fhir.model.dstu.resource.Binary; -import ca.uhn.fhir.model.dstu.resource.Organization; -import ca.uhn.fhir.model.dstu.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.util.PortUtil; -import ca.uhn.fhir.util.TestUtil; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.*; public class DefaultEncodingTest { diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReadDstu1Test.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReadDstu1Test.java index deeb32b5f86..78626745bdd 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReadDstu1Test.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReadDstu1Test.java @@ -1,11 +1,17 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.net.URLEncoder; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.composite.IdentifierDt; +import ca.uhn.fhir.model.dstu.resource.BaseResource; +import ca.uhn.fhir.model.dstu.resource.Binary; +import ca.uhn.fhir.model.dstu.resource.Organization; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HttpResponse; @@ -20,18 +26,10 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu.composite.IdentifierDt; -import ca.uhn.fhir.model.dstu.resource.BaseResource; -import ca.uhn.fhir.model.dstu.resource.Binary; -import ca.uhn.fhir.model.dstu.resource.Organization; -import ca.uhn.fhir.model.dstu.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.util.PortUtil; -import ca.uhn.fhir.util.TestUtil; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; public class ReadDstu1Test { @@ -129,10 +127,10 @@ public class ReadDstu1Test { @Test public void testReadWithEscapedCharsInId() throws Exception { String id = "ABC!@#$--DEF"; - String idEscaped = URLEncoder.encode(id, "UTF-8"); + String idEscaped = UrlUtil.escapeid); String vid = "GHI:/:/JKL"; - String vidEscaped = URLEncoder.encode(vid, "UTF-8"); + String vidEscaped = UrlUtil.escape(vid); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/" + idEscaped + "/_history/" + vidEscaped); HttpResponse status = ourClient.execute(httpGet); diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReferenceParameterTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReferenceParameterTest.java index 8a742b5e9e5..3c5aa97435e 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReferenceParameterTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReferenceParameterTest.java @@ -1,41 +1,12 @@ package ca.uhn.fhir.rest.server; -import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; -import ca.uhn.fhir.model.dstu.resource.Conformance; +import ca.uhn.fhir.model.dstu.resource.*; import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource; import ca.uhn.fhir.model.dstu.resource.Conformance.RestResourceSearchParam; -import ca.uhn.fhir.model.dstu.resource.Location; -import ca.uhn.fhir.model.dstu.resource.Observation; -import ca.uhn.fhir.model.dstu.resource.Organization; -import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.valueset.ResourceTypeEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.IdParam; @@ -47,6 +18,27 @@ import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; /** * Created by dsotnikov on 2/25/2014. @@ -105,7 +97,7 @@ public class ReferenceParameterTest { @Test public void testReferenceParamViewToken() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?provider.name=" + URLEncoder.encode("foo|bar", "UTF-8")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?provider.name=" + UrlUtil.escape("foo|bar")); HttpResponse status = ourClient.execute(httpGet); IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2_1Test.java index 8a5b7058401..030ef32baa2 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2_1Test.java @@ -105,7 +105,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test { @Test public void testEscapedOperationName() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/%24andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escape("VALTOK2A|VALTOK2B")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/%24andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escapeUrlParam("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escapeUrlParam("VALTOK2A|VALTOK2B")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -118,7 +118,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test { @Test public void testAndListWithUrl() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escape("VALTOK2A|VALTOK2B")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escapeUrlParam("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escapeUrlParam("VALTOK2A|VALTOK2B")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -270,7 +270,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test { } @Test public void testNonRepeatingWithUrl() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr=VALSTR&valtok=" + UrlUtil.escape("VALTOKA|VALTOKB")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr=VALSTR&valtok=" + UrlUtil.escapeUrlParam("VALTOKA|VALTOKB")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -290,7 +290,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test { @Test public void testNonRepeatingWithUrlQualified() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr:exact=VALSTR&valtok:not=" + UrlUtil.escape("VALTOKA|VALTOKB")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr:exact=VALSTR&valtok:not=" + UrlUtil.escapeUrlParam("VALTOKA|VALTOKB")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -345,7 +345,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test { @Test public void testOrListWithUrl() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$orlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escape("VALTOK2A|VALTOK2B")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$orlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escapeUrlParam("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escapeUrlParam("VALTOK2A|VALTOK2B")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2Test.java index 633302c1b4d..08bd9b6f10a 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2Test.java @@ -110,8 +110,8 @@ public class OperationServerWithSearchParamTypesDstu2Test { @Test public void testAndListWithUrl() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" - + UrlUtil.escape("VALTOK2A|VALTOK2B")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escapeUrlParam("VALTOK1A|VALTOK1B") + "&valtok=" + + UrlUtil.escapeUrlParam("VALTOK2A|VALTOK2B")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -214,7 +214,7 @@ public class OperationServerWithSearchParamTypesDstu2Test { @Test public void testNonRepeatingWithUrl() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr=VALSTR&valtok=" + UrlUtil.escape("VALTOKA|VALTOKB")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr=VALSTR&valtok=" + UrlUtil.escapeUrlParam("VALTOKA|VALTOKB")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -234,7 +234,7 @@ public class OperationServerWithSearchParamTypesDstu2Test { @Test public void testNonRepeatingWithUrlQualified() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr:exact=VALSTR&valtok:not=" + UrlUtil.escape("VALTOKA|VALTOKB")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr:exact=VALSTR&valtok:not=" + UrlUtil.escapeUrlParam("VALTOKA|VALTOKB")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -289,8 +289,8 @@ public class OperationServerWithSearchParamTypesDstu2Test { @Test public void testOrListWithUrl() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$orlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" - + UrlUtil.escape("VALTOK2A|VALTOK2B")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$orlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escapeUrlParam("VALTOK1A|VALTOK1B") + "&valtok=" + + UrlUtil.escapeUrlParam("VALTOK2A|VALTOK2B")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerSearchDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerSearchDstu2Test.java index 7617f7838e9..36d9e0aba94 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerSearchDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerSearchDstu2Test.java @@ -1,12 +1,14 @@ package ca.uhn.fhir.rest.server; -import static org.junit.Assert.assertEquals; - -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; @@ -22,14 +24,11 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.param.ReferenceParam; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.util.PortUtil; -import ca.uhn.fhir.util.TestUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; public class ServerSearchDstu2Test { @@ -106,7 +105,7 @@ public class ServerSearchDstu2Test { @Test public void testSearchWithEncodedValue() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/?param1=" + URLEncoder.encode("Jernelöv", "UTF-8")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/?param1=" + UrlUtil.escapeUrlParam("Jernelöv")); HttpResponse status = ourClient.execute(httpGet); String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java index 3b2818641d3..cd2543b912c 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java @@ -14,7 +14,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; @@ -186,7 +185,7 @@ public class ResponseHighlightingInterceptorTest { @Test public void testForceApplicationJsonPlusFhir() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=" + UrlUtil.escape("application/json+fhir")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=" + UrlUtil.escapeUrlParam("application/json+fhir")); httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"); HttpResponse status = ourClient.execute(httpGet); @@ -225,7 +224,7 @@ public class ResponseHighlightingInterceptorTest { @Test public void testForceApplicationXmlPlusFhir() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=" + UrlUtil.escape("application/xml+fhir")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=" + UrlUtil.escapeUrlParam("application/xml+fhir")); httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"); HttpResponse status = ourClient.execute(httpGet); diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/codesystems/MapTransformEnumFactory.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/codesystems/MapTransformEnumFactory.java index 64fc92fd21b..b4f17d41118 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/codesystems/MapTransformEnumFactory.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/codesystems/MapTransformEnumFactory.java @@ -1,122 +1,122 @@ -package org.hl7.fhir.dstu3.model.codesystems; - -/* - Copyright (c) 2011+, HL7, Inc. - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - -*/ - -// Generated on Sat, Mar 25, 2017 21:03-0400 for FHIR v3.0.0 - - -import org.hl7.fhir.dstu3.model.EnumFactory; - -public class MapTransformEnumFactory implements EnumFactory { - - public MapTransform fromCode(String codeString) throws IllegalArgumentException { - if (codeString == null || "".equals(codeString)) - return null; - if ("create".equals(codeString)) - return MapTransform.CREATE; - if ("copy".equals(codeString)) - return MapTransform.COPY; - if ("truncate".equals(codeString)) - return MapTransform.TRUNCATE; - if ("escape".equals(codeString)) - return MapTransform.ESCAPE; - if ("cast".equals(codeString)) - return MapTransform.CAST; - if ("append".equals(codeString)) - return MapTransform.APPEND; - if ("translate".equals(codeString)) - return MapTransform.TRANSLATE; - if ("reference".equals(codeString)) - return MapTransform.REFERENCE; - if ("dateOp".equals(codeString)) - return MapTransform.DATEOP; - if ("uuid".equals(codeString)) - return MapTransform.UUID; - if ("pointer".equals(codeString)) - return MapTransform.POINTER; - if ("evaluate".equals(codeString)) - return MapTransform.EVALUATE; - if ("cc".equals(codeString)) - return MapTransform.CC; - if ("c".equals(codeString)) - return MapTransform.C; - if ("qty".equals(codeString)) - return MapTransform.QTY; - if ("id".equals(codeString)) - return MapTransform.ID; - if ("cp".equals(codeString)) - return MapTransform.CP; - throw new IllegalArgumentException("Unknown MapTransform code '"+codeString+"'"); - } - - public String toCode(MapTransform code) { - if (code == MapTransform.CREATE) - return "create"; - if (code == MapTransform.COPY) - return "copy"; - if (code == MapTransform.TRUNCATE) - return "truncate"; - if (code == MapTransform.ESCAPE) - return "escape"; - if (code == MapTransform.CAST) - return "cast"; - if (code == MapTransform.APPEND) - return "append"; - if (code == MapTransform.TRANSLATE) - return "translate"; - if (code == MapTransform.REFERENCE) - return "reference"; - if (code == MapTransform.DATEOP) - return "dateOp"; - if (code == MapTransform.UUID) - return "uuid"; - if (code == MapTransform.POINTER) - return "pointer"; - if (code == MapTransform.EVALUATE) - return "evaluate"; - if (code == MapTransform.CC) - return "cc"; - if (code == MapTransform.C) - return "c"; - if (code == MapTransform.QTY) - return "qty"; - if (code == MapTransform.ID) - return "id"; - if (code == MapTransform.CP) - return "cp"; - return "?"; - } - - public String toSystem(MapTransform code) { - return code.getSystem(); - } - -} - +package org.hl7.fhir.dstu3.model.codesystems; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +*/ + +// Generated on Sat, Mar 25, 2017 21:03-0400 for FHIR v3.0.0 + + +import org.hl7.fhir.dstu3.model.EnumFactory; + +public class MapTransformEnumFactory implements EnumFactory { + + public MapTransform fromCode(String codeString) throws IllegalArgumentException { + if (codeString == null || "".equals(codeString)) + return null; + if ("create".equals(codeString)) + return MapTransform.CREATE; + if ("copy".equals(codeString)) + return MapTransform.COPY; + if ("truncate".equals(codeString)) + return MapTransform.TRUNCATE; + if ("escapeUrlParam".equals(codeString)) + return MapTransform.ESCAPE; + if ("cast".equals(codeString)) + return MapTransform.CAST; + if ("append".equals(codeString)) + return MapTransform.APPEND; + if ("translate".equals(codeString)) + return MapTransform.TRANSLATE; + if ("reference".equals(codeString)) + return MapTransform.REFERENCE; + if ("dateOp".equals(codeString)) + return MapTransform.DATEOP; + if ("uuid".equals(codeString)) + return MapTransform.UUID; + if ("pointer".equals(codeString)) + return MapTransform.POINTER; + if ("evaluate".equals(codeString)) + return MapTransform.EVALUATE; + if ("cc".equals(codeString)) + return MapTransform.CC; + if ("c".equals(codeString)) + return MapTransform.C; + if ("qty".equals(codeString)) + return MapTransform.QTY; + if ("id".equals(codeString)) + return MapTransform.ID; + if ("cp".equals(codeString)) + return MapTransform.CP; + throw new IllegalArgumentException("Unknown MapTransform code '"+codeString+"'"); + } + + public String toCode(MapTransform code) { + if (code == MapTransform.CREATE) + return "create"; + if (code == MapTransform.COPY) + return "copy"; + if (code == MapTransform.TRUNCATE) + return "truncate"; + if (code == MapTransform.ESCAPE) + return "escapeUrlParam"; + if (code == MapTransform.CAST) + return "cast"; + if (code == MapTransform.APPEND) + return "append"; + if (code == MapTransform.TRANSLATE) + return "translate"; + if (code == MapTransform.REFERENCE) + return "reference"; + if (code == MapTransform.DATEOP) + return "dateOp"; + if (code == MapTransform.UUID) + return "uuid"; + if (code == MapTransform.POINTER) + return "pointer"; + if (code == MapTransform.EVALUATE) + return "evaluate"; + if (code == MapTransform.CC) + return "cc"; + if (code == MapTransform.C) + return "c"; + if (code == MapTransform.QTY) + return "qty"; + if (code == MapTransform.ID) + return "id"; + if (code == MapTransform.CP) + return "cp"; + return "?"; + } + + public String toSystem(MapTransform code) { + return code.getSystem(); + } + +} + diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu3Test.java index 68235bea36a..9566989b0a0 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu3Test.java @@ -105,7 +105,7 @@ public class OperationServerWithSearchParamTypesDstu3Test { @Test public void testEscapedOperationName() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/%24andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escape("VALTOK2A|VALTOK2B")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/%24andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escapeUrlParam("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escapeUrlParam("VALTOK2A|VALTOK2B")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -118,7 +118,7 @@ public class OperationServerWithSearchParamTypesDstu3Test { @Test public void testAndListWithUrl() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escape("VALTOK2A|VALTOK2B")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escapeUrlParam("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escapeUrlParam("VALTOK2A|VALTOK2B")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -270,7 +270,7 @@ public class OperationServerWithSearchParamTypesDstu3Test { } @Test public void testNonRepeatingWithUrl() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr=VALSTR&valtok=" + UrlUtil.escape("VALTOKA|VALTOKB")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr=VALSTR&valtok=" + UrlUtil.escapeUrlParam("VALTOKA|VALTOKB")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -290,7 +290,7 @@ public class OperationServerWithSearchParamTypesDstu3Test { @Test public void testNonRepeatingWithUrlQualified() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr:exact=VALSTR&valtok:not=" + UrlUtil.escape("VALTOKA|VALTOKB")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr:exact=VALSTR&valtok:not=" + UrlUtil.escapeUrlParam("VALTOKA|VALTOKB")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); @@ -345,7 +345,7 @@ public class OperationServerWithSearchParamTypesDstu3Test { @Test public void testOrListWithUrl() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$orlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escape("VALTOK2A|VALTOK2B")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$orlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escapeUrlParam("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escapeUrlParam("VALTOK2A|VALTOK2B")); HttpResponse status = ourClient.execute(httpGet); assertEquals(200, status.getStatusLine().getStatusCode()); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchDstu3Test.java index a8a0128f563..351328a4c89 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchDstu3Test.java @@ -137,25 +137,25 @@ public class SearchDstu3Test { httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=" + UrlUtil.escape(Constants.CT_FHIR_JSON_NEW))); + assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); // Fetch the next page httpGet = new HttpGet(linkNext); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=" + UrlUtil.escape(Constants.CT_FHIR_JSON_NEW))); + assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); // Fetch the next page httpGet = new HttpGet(linkNext); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=" + UrlUtil.escape(Constants.CT_FHIR_JSON_NEW))); + assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); // Fetch the next page httpGet = new HttpGet(linkNext); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=" + UrlUtil.escape(Constants.CT_FHIR_JSON_NEW))); + assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); } diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/utilities/xml/XMLUtil.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/utilities/xml/XMLUtil.java index c279b41c0f8..98c81ddc3ea 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/utilities/xml/XMLUtil.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/utilities/xml/XMLUtil.java @@ -1,406 +1,406 @@ -/* -Copyright (c) 2011+, HL7, Inc -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ -package org.hl7.fhir.instance.utilities.xml; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.List; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.hl7.fhir.instance.utilities.Utilities; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.ls.DOMImplementationLS; -import org.w3c.dom.ls.LSSerializer; - -public class XMLUtil { - - public static final String SPACE_CHAR = "\u00A0"; - - public static boolean isNMToken(String name) { - if (name == null) - return false; - for (int i = 0; i < name.length(); i++) - if (!isNMTokenChar(name.charAt(i))) - return false; - return name.length() > 0; - } - - public static boolean isNMTokenChar(char c) { - return isLetter(c) || isDigit(c) || c == '.' || c == '-' || c == '_' || c == ':' || isCombiningChar(c) || isExtender(c); - } - - private static boolean isDigit(char c) { - return (c >= '\u0030' && c <= '\u0039') || (c >= '\u0660' && c <= '\u0669') || (c >= '\u06F0' && c <= '\u06F9') || - (c >= '\u0966' && c <= '\u096F') || (c >= '\u09E6' && c <= '\u09EF') || (c >= '\u0A66' && c <= '\u0A6F') || - (c >= '\u0AE6' && c <= '\u0AEF') || (c >= '\u0B66' && c <= '\u0B6F') || (c >= '\u0BE7' && c <= '\u0BEF') || - (c >= '\u0C66' && c <= '\u0C6F') || (c >= '\u0CE6' && c <= '\u0CEF') || (c >= '\u0D66' && c <= '\u0D6F') || - (c >= '\u0E50' && c <= '\u0E59') || (c >= '\u0ED0' && c <= '\u0ED9') || (c >= '\u0F20' && c <= '\u0F29'); - } - - private static boolean isCombiningChar(char c) { - return (c >= '\u0300' && c <= '\u0345') || (c >= '\u0360' && c <= '\u0361') || (c >= '\u0483' && c <= '\u0486') || - (c >= '\u0591' && c <= '\u05A1') || (c >= '\u05A3' && c <= '\u05B9') || (c >= '\u05BB' && c <= '\u05BD') || - c == '\u05BF' || (c >= '\u05C1' && c <= '\u05C2') || c == '\u05C4' || (c >= '\u064B' && c <= '\u0652') || - c == '\u0670' || (c >= '\u06D6' && c <= '\u06DC') || (c >= '\u06DD' && c <= '\u06DF') || (c >= '\u06E0' && c <= '\u06E4') || - (c >= '\u06E7' && c <= '\u06E8') || (c >= '\u06EA' && c <= '\u06ED') || (c >= '\u0901' && c <= '\u0903') || c == '\u093C' || - (c >= '\u093E' && c <= '\u094C') || c == '\u094D' || (c >= '\u0951' && c <= '\u0954') || (c >= '\u0962' && c <= '\u0963') || - (c >= '\u0981' && c <= '\u0983') || c == '\u09BC' || c == '\u09BE' || c == '\u09BF' || (c >= '\u09C0' && c <= '\u09C4') || - (c >= '\u09C7' && c <= '\u09C8') || (c >= '\u09CB' && c <= '\u09CD') || c == '\u09D7' || (c >= '\u09E2' && c <= '\u09E3') || - c == '\u0A02' || c == '\u0A3C' || c == '\u0A3E' || c == '\u0A3F' || (c >= '\u0A40' && c <= '\u0A42') || - (c >= '\u0A47' && c <= '\u0A48') || (c >= '\u0A4B' && c <= '\u0A4D') || (c >= '\u0A70' && c <= '\u0A71') || - (c >= '\u0A81' && c <= '\u0A83') || c == '\u0ABC' || (c >= '\u0ABE' && c <= '\u0AC5') || (c >= '\u0AC7' && c <= '\u0AC9') || - (c >= '\u0ACB' && c <= '\u0ACD') || (c >= '\u0B01' && c <= '\u0B03') || c == '\u0B3C' || (c >= '\u0B3E' && c <= '\u0B43') || - (c >= '\u0B47' && c <= '\u0B48') || (c >= '\u0B4B' && c <= '\u0B4D') || (c >= '\u0B56' && c <= '\u0B57') || - (c >= '\u0B82' && c <= '\u0B83') || (c >= '\u0BBE' && c <= '\u0BC2') || (c >= '\u0BC6' && c <= '\u0BC8') || - (c >= '\u0BCA' && c <= '\u0BCD') || c == '\u0BD7' || (c >= '\u0C01' && c <= '\u0C03') || (c >= '\u0C3E' && c <= '\u0C44') || - (c >= '\u0C46' && c <= '\u0C48') || (c >= '\u0C4A' && c <= '\u0C4D') || (c >= '\u0C55' && c <= '\u0C56') || - (c >= '\u0C82' && c <= '\u0C83') || (c >= '\u0CBE' && c <= '\u0CC4') || (c >= '\u0CC6' && c <= '\u0CC8') || - (c >= '\u0CCA' && c <= '\u0CCD') || (c >= '\u0CD5' && c <= '\u0CD6') || (c >= '\u0D02' && c <= '\u0D03') || - (c >= '\u0D3E' && c <= '\u0D43') || (c >= '\u0D46' && c <= '\u0D48') || (c >= '\u0D4A' && c <= '\u0D4D') || c == '\u0D57' || - c == '\u0E31' || (c >= '\u0E34' && c <= '\u0E3A') || (c >= '\u0E47' && c <= '\u0E4E') || c == '\u0EB1' || - (c >= '\u0EB4' && c <= '\u0EB9') || (c >= '\u0EBB' && c <= '\u0EBC') || (c >= '\u0EC8' && c <= '\u0ECD') || - (c >= '\u0F18' && c <= '\u0F19') || c == '\u0F35' || c == '\u0F37' || c == '\u0F39' || c == '\u0F3E' || c == '\u0F3F' || - (c >= '\u0F71' && c <= '\u0F84') || (c >= '\u0F86' && c <= '\u0F8B') || (c >= '\u0F90' && c <= '\u0F95') || c == '\u0F97' || - (c >= '\u0F99' && c <= '\u0FAD') || (c >= '\u0FB1' && c <= '\u0FB7') || c == '\u0FB9' || (c >= '\u20D0' && c <= '\u20DC') || - c == '\u20E1' || (c >= '\u302A' && c <= '\u302F') || c == '\u3099' || c == '\u309A'; - } - - private static boolean isExtender(char c) { - return c == '\u00B7' || c == '\u02D0' || c == '\u02D1' || c == '\u0387' || c == '\u0640' || c == '\u0E46' || - c == '\u0EC6' || c == '\u3005' || (c >= '\u3031' && c <= '\u3035') || (c >= '\u309D' && c <= '\u309E') || - (c >= '\u30FC' && c <= '\u30FE'); - } - - private static boolean isLetter(char c) { - return isBaseChar(c) || isIdeographic(c); - } - - private static boolean isBaseChar(char c) { - return (c >= '\u0041' && c <= '\u005A') || (c >= '\u0061' && c <= '\u007A') || (c >= '\u00C0' && c <= '\u00D6') || - (c >= '\u00D8' && c <= '\u00F6') || (c >= '\u00F8' && c <= '\u00FF') || (c >= '\u0100' && c <= '\u0131') || - (c >= '\u0134' && c <= '\u013E') || (c >= '\u0141' && c <= '\u0148') || (c >= '\u014A' && c <= '\u017E') || - (c >= '\u0180' && c <= '\u01C3') || (c >= '\u01CD' && c <= '\u01F0') || (c >= '\u01F4' && c <= '\u01F5') || - (c >= '\u01FA' && c <= '\u0217') || (c >= '\u0250' && c <= '\u02A8') || (c >= '\u02BB' && c <= '\u02C1') || - c == '\u0386' || (c >= '\u0388' && c <= '\u038A') || c == '\u038C' || (c >= '\u038E' && c <= '\u03A1') || - (c >= '\u03A3' && c <= '\u03CE') || (c >= '\u03D0' && c <= '\u03D6') || c == '\u03DA' || c == '\u03DC' || c == '\u03DE' || - c == '\u03E0' || (c >= '\u03E2' && c <= '\u03F3') || (c >= '\u0401' && c <= '\u040C') || (c >= '\u040E' && c <= '\u044F') || - (c >= '\u0451' && c <= '\u045C') || (c >= '\u045E' && c <= '\u0481') || (c >= '\u0490' && c <= '\u04C4') || - (c >= '\u04C7' && c <= '\u04C8') || (c >= '\u04CB' && c <= '\u04CC') || (c >= '\u04D0' && c <= '\u04EB') || - (c >= '\u04EE' && c <= '\u04F5') || (c >= '\u04F8' && c <= '\u04F9') || (c >= '\u0531' && c <= '\u0556') || - c == '\u0559' || (c >= '\u0561' && c <= '\u0586') || (c >= '\u05D0' && c <= '\u05EA') || (c >= '\u05F0' && c <= '\u05F2') || - (c >= '\u0621' && c <= '\u063A') || (c >= '\u0641' && c <= '\u064A') || (c >= '\u0671' && c <= '\u06B7') || - (c >= '\u06BA' && c <= '\u06BE') || (c >= '\u06C0' && c <= '\u06CE') || (c >= '\u06D0' && c <= '\u06D3') || - c == '\u06D5' || (c >= '\u06E5' && c <= '\u06E6') || (c >= '\u0905' && c <= '\u0939') || c == '\u093D' || - (c >= '\u0958' && c <= '\u0961') || (c >= '\u0985' && c <= '\u098C') || (c >= '\u098F' && c <= '\u0990') || - (c >= '\u0993' && c <= '\u09A8') || (c >= '\u09AA' && c <= '\u09B0') || c == '\u09B2' || - (c >= '\u09B6' && c <= '\u09B9') || (c >= '\u09DC' && c <= '\u09DD') || (c >= '\u09DF' && c <= '\u09E1') || - (c >= '\u09F0' && c <= '\u09F1') || (c >= '\u0A05' && c <= '\u0A0A') || (c >= '\u0A0F' && c <= '\u0A10') || - (c >= '\u0A13' && c <= '\u0A28') || (c >= '\u0A2A' && c <= '\u0A30') || (c >= '\u0A32' && c <= '\u0A33') || - (c >= '\u0A35' && c <= '\u0A36') || (c >= '\u0A38' && c <= '\u0A39') || (c >= '\u0A59' && c <= '\u0A5C') || - c == '\u0A5E' || (c >= '\u0A72' && c <= '\u0A74') || (c >= '\u0A85' && c <= '\u0A8B') || c == '\u0A8D' || - (c >= '\u0A8F' && c <= '\u0A91') || (c >= '\u0A93' && c <= '\u0AA8') || (c >= '\u0AAA' && c <= '\u0AB0') || - (c >= '\u0AB2' && c <= '\u0AB3') || (c >= '\u0AB5' && c <= '\u0AB9') || c == '\u0ABD' || c == '\u0AE0' || - (c >= '\u0B05' && c <= '\u0B0C') || (c >= '\u0B0F' && c <= '\u0B10') || (c >= '\u0B13' && c <= '\u0B28') || - (c >= '\u0B2A' && c <= '\u0B30') || (c >= '\u0B32' && c <= '\u0B33') || (c >= '\u0B36' && c <= '\u0B39') || - c == '\u0B3D' || (c >= '\u0B5C' && c <= '\u0B5D') || (c >= '\u0B5F' && c <= '\u0B61') || - (c >= '\u0B85' && c <= '\u0B8A') || (c >= '\u0B8E' && c <= '\u0B90') || (c >= '\u0B92' && c <= '\u0B95') || - (c >= '\u0B99' && c <= '\u0B9A') || c == '\u0B9C' || (c >= '\u0B9E' && c <= '\u0B9F') || - (c >= '\u0BA3' && c <= '\u0BA4') || (c >= '\u0BA8' && c <= '\u0BAA') || (c >= '\u0BAE' && c <= '\u0BB5') || - (c >= '\u0BB7' && c <= '\u0BB9') || (c >= '\u0C05' && c <= '\u0C0C') || (c >= '\u0C0E' && c <= '\u0C10') || - (c >= '\u0C12' && c <= '\u0C28') || (c >= '\u0C2A' && c <= '\u0C33') || (c >= '\u0C35' && c <= '\u0C39') || - (c >= '\u0C60' && c <= '\u0C61') || (c >= '\u0C85' && c <= '\u0C8C') || (c >= '\u0C8E' && c <= '\u0C90') || - (c >= '\u0C92' && c <= '\u0CA8') || (c >= '\u0CAA' && c <= '\u0CB3') || (c >= '\u0CB5' && c <= '\u0CB9') || - c == '\u0CDE' || (c >= '\u0CE0' && c <= '\u0CE1') || (c >= '\u0D05' && c <= '\u0D0C') || - (c >= '\u0D0E' && c <= '\u0D10') || (c >= '\u0D12' && c <= '\u0D28') || (c >= '\u0D2A' && c <= '\u0D39') || - (c >= '\u0D60' && c <= '\u0D61') || (c >= '\u0E01' && c <= '\u0E2E') || c == '\u0E30' || - (c >= '\u0E32' && c <= '\u0E33') || (c >= '\u0E40' && c <= '\u0E45') || (c >= '\u0E81' && c <= '\u0E82') || - c == '\u0E84' || (c >= '\u0E87' && c <= '\u0E88') || c == '\u0E8A' || c == '\u0E8D' || (c >= '\u0E94' && c <= '\u0E97') || - (c >= '\u0E99' && c <= '\u0E9F') || (c >= '\u0EA1' && c <= '\u0EA3') || c == '\u0EA5' || c == '\u0EA7' || - (c >= '\u0EAA' && c <= '\u0EAB') || (c >= '\u0EAD' && c <= '\u0EAE') || c == '\u0EB0' || - (c >= '\u0EB2' && c <= '\u0EB3') || c == '\u0EBD' || (c >= '\u0EC0' && c <= '\u0EC4') || - (c >= '\u0F40' && c <= '\u0F47') || (c >= '\u0F49' && c <= '\u0F69') || (c >= '\u10A0' && c <= '\u10C5') || - (c >= '\u10D0' && c <= '\u10F6') || c == '\u1100' || (c >= '\u1102' && c <= '\u1103') || - (c >= '\u1105' && c <= '\u1107') || c == '\u1109' || (c >= '\u110B' && c <= '\u110C') || - (c >= '\u110E' && c <= '\u1112') || c == '\u113C' || c == '\u113E' || c == '\u1140' || c == '\u114C' || - c == '\u114E' || c == '\u1150' || (c >= '\u1154' && c <= '\u1155') || c == '\u1159' || - (c >= '\u115F' && c <= '\u1161') || c == '\u1163' || c == '\u1165' || c == '\u1167' || c == '\u1169' || - (c >= '\u116D' && c <= '\u116E') || (c >= '\u1172' && c <= '\u1173') || c == '\u1175' || - c == '\u119E' || c == '\u11A8' || c == '\u11AB' || (c >= '\u11AE' && c <= '\u11AF') || - (c >= '\u11B7' && c <= '\u11B8') || c == '\u11BA' || (c >= '\u11BC' && c <= '\u11C2') || - c == '\u11EB' || c == '\u11F0' || c == '\u11F9' || (c >= '\u1E00' && c <= '\u1E9B') || (c >= '\u1EA0' && c <= '\u1EF9') || - (c >= '\u1F00' && c <= '\u1F15') || (c >= '\u1F18' && c <= '\u1F1D') || (c >= '\u1F20' && c <= '\u1F45') || - (c >= '\u1F48' && c <= '\u1F4D') || (c >= '\u1F50' && c <= '\u1F57') || c == '\u1F59' || c == '\u1F5B' || c == '\u1F5D' || - (c >= '\u1F5F' && c <= '\u1F7D') || (c >= '\u1F80' && c <= '\u1FB4') || (c >= '\u1FB6' && c <= '\u1FBC') || - c == '\u1FBE' || (c >= '\u1FC2' && c <= '\u1FC4') || (c >= '\u1FC6' && c <= '\u1FCC') || - (c >= '\u1FD0' && c <= '\u1FD3') || (c >= '\u1FD6' && c <= '\u1FDB') || (c >= '\u1FE0' && c <= '\u1FEC') || - (c >= '\u1FF2' && c <= '\u1FF4') || (c >= '\u1FF6' && c <= '\u1FFC') || c == '\u2126' || - (c >= '\u212A' && c <= '\u212B') || c == '\u212E' || (c >= '\u2180' && c <= '\u2182') || - (c >= '\u3041' && c <= '\u3094') || (c >= '\u30A1' && c <= '\u30FA') || (c >= '\u3105' && c <= '\u312C') || - (c >= '\uAC00' && c <= '\uD7A3'); - } - - private static boolean isIdeographic(char c) { - return (c >= '\u4E00' && c <= '\u9FA5') || c == '\u3007' || (c >= '\u3021' && c <= '\u3029'); - } - - public static String determineEncoding(InputStream stream) throws IOException { - stream.mark(20000); - try { - int b0 = stream.read(); - int b1 = stream.read(); - int b2 = stream.read(); - int b3 = stream.read(); - - if (b0 == 0xFE && b1 == 0xFF) - return "UTF-16BE"; - else if (b0 == 0xFF && b1 == 0xFE) - return "UTF-16LE"; - else if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF ) - return "UTF-8"; - else if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) - return "UTF-16BE"; - else if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) - return "UTF-16LE"; - else if (b0 == 0x3C && b1 == 0x3F && b2 == 0x78 && b3 == 0x6D) { -// UTF-8, ISO 646, ASCII, some part of ISO 8859, Shift-JIS, EUC, or any other 7-bit, 8-bit, or mixed-width encoding -// which ensures that the characters of ASCII have their normal positions, width, and values; the actual encoding -// declaration must be read to detect which of these applies, but since all of these encodings use the same bit patterns -// for the relevant ASCII characters, the encoding declaration itself may be read reliably - InputStreamReader rdr = new InputStreamReader(stream, "US-ASCII"); - String hdr = readFirstLine(rdr); - return extractEncoding(hdr); - } else - return null; - } finally { - stream.reset(); - } - } - - private static String extractEncoding(String hdr) { - int i = hdr.indexOf("encoding="); - if (i == -1) - return null; - hdr = hdr.substring(i+9); - char sep = hdr.charAt(0); - hdr = hdr.substring(1); - i = hdr.indexOf(sep); - if (i == -1) - return null; - return hdr.substring(0, i); - } - - private static String readFirstLine(InputStreamReader rdr) throws IOException { - char[] buf = new char[1]; - StringBuffer bldr = new StringBuffer(); - rdr.read(buf); - while (buf[0] != '>') { - bldr.append(buf[0]); - rdr.read(buf); - } - return bldr.toString(); - } - - - public static boolean charSetImpliesAscii(String charset) { - return charset.equals("ISO-8859-1") || charset.equals("US-ASCII"); - } - - - /** - * Converts the raw characters to XML escape characters. - * - * @param rawContent - * @param charset Null when charset is not known, so we assume it's unicode - * @param isNoLines - * @return escape string - */ - public static String escapeXML(String rawContent, String charset, boolean isNoLines) { - if (rawContent == null) - return ""; - else { - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < rawContent.length(); i++) { - char ch = rawContent.charAt(i); - if (ch == '\'') - sb.append("'"); - else if (ch == '&') - sb.append("&"); - else if (ch == '"') - sb.append("""); - else if (ch == '<') - sb.append("<"); - else if (ch == '>') - sb.append(">"); - else if (ch > '~' && charset != null && charSetImpliesAscii(charset)) - // TODO - why is hashcode the only way to get the unicode number for the character - // in jre 5.0? - sb.append("&#x"+Integer.toHexString(new Character(ch).hashCode()).toUpperCase()+";"); - else if (isNoLines) { - if (ch == '\r') - sb.append(" "); - else if (ch != '\n') - sb.append(ch); - } - else - sb.append(ch); - } - return sb.toString(); - } - } - - public static Element getFirstChild(Element e) { - if (e == null) - return null; - Node n = e.getFirstChild(); - while (n != null && n.getNodeType() != Node.ELEMENT_NODE) - n = n.getNextSibling(); - return (Element) n; - } - - public static Element getNamedChild(Element e, String name) { - Element c = getFirstChild(e); - while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName())) - c = getNextSibling(c); - return c; - } - - public static Element getNextSibling(Element e) { - Node n = e.getNextSibling(); - while (n != null && n.getNodeType() != Node.ELEMENT_NODE) - n = n.getNextSibling(); - return (Element) n; - } - - public static void getNamedChildren(Element e, String name, List set) { - Element c = getFirstChild(e); - while (c != null) { - if (name.equals(c.getLocalName()) || name.equals(c.getNodeName()) ) - set.add(c); - c = getNextSibling(c); - } - } - - public static String htmlToXmlEscapedPlainText(Element r) { - StringBuilder s = new StringBuilder(); - Node n = r.getFirstChild(); - boolean ws = false; - while (n != null) { - if (n.getNodeType() == Node.TEXT_NODE) { - String t = n.getTextContent().trim(); - if (Utilities.noString(t)) - ws = true; - else { - if (ws) - s.append(" "); - ws = false; - s.append(t); - } - } - if (n.getNodeType() == Node.ELEMENT_NODE) { - if (ws) - s.append(" "); - ws = false; - s.append(htmlToXmlEscapedPlainText((Element) n)); - if (r.getNodeName().equals("br") || r.getNodeName().equals("p")) - s.append("\r\n"); - } - n = n.getNextSibling(); - } - return s.toString(); - } - - public static String htmlToXmlEscapedPlainText(String definition) throws Exception { - return htmlToXmlEscapedPlainText(parseToDom("
    "+definition+"
    ").getDocumentElement()); - } - - public static String elementToString(Element el) { - if (el == null) - return ""; - Document document = el.getOwnerDocument(); - DOMImplementationLS domImplLS = (DOMImplementationLS) document - .getImplementation(); - LSSerializer serializer = domImplLS.createLSSerializer(); - return serializer.writeToString(el); - } - - public static String getNamedChildValue(Element element, String name) { - Element e = getNamedChild(element, name); - return e == null ? null : e.getAttribute("value"); - } - - public static void getNamedChildrenWithWildcard(Element focus, String name, List children) { - Element c = getFirstChild(focus); - while (c != null) { - String n = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); - if (name.equals(n) || (name.endsWith("[x]") && n.startsWith(name.substring(0, name.length()-3)))) - children.add(c); - c = getNextSibling(c); - } - - } - - public static boolean hasNamedChild(Element e, String name) { - Element c = getFirstChild(e); - while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName())) - c = getNextSibling(c); - return c != null; - } - - public static Document parseToDom(String content) throws Exception { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(false); - DocumentBuilder builder = factory.newDocumentBuilder(); - return builder.parse(new ByteArrayInputStream(content.getBytes())); - } - - public static Element getLastChild(Element e) { - if (e == null) - return null; - Node n = e.getLastChild(); - while (n != null && n.getNodeType() != Node.ELEMENT_NODE) - n = n.getPreviousSibling(); - return (Element) n; - } - - public static Element getPrevSibling(Element e) { - Node n = e.getPreviousSibling(); - while (n != null && n.getNodeType() != Node.ELEMENT_NODE) - n = n.getPreviousSibling(); - return (Element) n; - } - - -} +/* +Copyright (c) 2011+, HL7, Inc +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +package org.hl7.fhir.instance.utilities.xml; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.hl7.fhir.instance.utilities.Utilities; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSSerializer; + +public class XMLUtil { + + public static final String SPACE_CHAR = "\u00A0"; + + public static boolean isNMToken(String name) { + if (name == null) + return false; + for (int i = 0; i < name.length(); i++) + if (!isNMTokenChar(name.charAt(i))) + return false; + return name.length() > 0; + } + + public static boolean isNMTokenChar(char c) { + return isLetter(c) || isDigit(c) || c == '.' || c == '-' || c == '_' || c == ':' || isCombiningChar(c) || isExtender(c); + } + + private static boolean isDigit(char c) { + return (c >= '\u0030' && c <= '\u0039') || (c >= '\u0660' && c <= '\u0669') || (c >= '\u06F0' && c <= '\u06F9') || + (c >= '\u0966' && c <= '\u096F') || (c >= '\u09E6' && c <= '\u09EF') || (c >= '\u0A66' && c <= '\u0A6F') || + (c >= '\u0AE6' && c <= '\u0AEF') || (c >= '\u0B66' && c <= '\u0B6F') || (c >= '\u0BE7' && c <= '\u0BEF') || + (c >= '\u0C66' && c <= '\u0C6F') || (c >= '\u0CE6' && c <= '\u0CEF') || (c >= '\u0D66' && c <= '\u0D6F') || + (c >= '\u0E50' && c <= '\u0E59') || (c >= '\u0ED0' && c <= '\u0ED9') || (c >= '\u0F20' && c <= '\u0F29'); + } + + private static boolean isCombiningChar(char c) { + return (c >= '\u0300' && c <= '\u0345') || (c >= '\u0360' && c <= '\u0361') || (c >= '\u0483' && c <= '\u0486') || + (c >= '\u0591' && c <= '\u05A1') || (c >= '\u05A3' && c <= '\u05B9') || (c >= '\u05BB' && c <= '\u05BD') || + c == '\u05BF' || (c >= '\u05C1' && c <= '\u05C2') || c == '\u05C4' || (c >= '\u064B' && c <= '\u0652') || + c == '\u0670' || (c >= '\u06D6' && c <= '\u06DC') || (c >= '\u06DD' && c <= '\u06DF') || (c >= '\u06E0' && c <= '\u06E4') || + (c >= '\u06E7' && c <= '\u06E8') || (c >= '\u06EA' && c <= '\u06ED') || (c >= '\u0901' && c <= '\u0903') || c == '\u093C' || + (c >= '\u093E' && c <= '\u094C') || c == '\u094D' || (c >= '\u0951' && c <= '\u0954') || (c >= '\u0962' && c <= '\u0963') || + (c >= '\u0981' && c <= '\u0983') || c == '\u09BC' || c == '\u09BE' || c == '\u09BF' || (c >= '\u09C0' && c <= '\u09C4') || + (c >= '\u09C7' && c <= '\u09C8') || (c >= '\u09CB' && c <= '\u09CD') || c == '\u09D7' || (c >= '\u09E2' && c <= '\u09E3') || + c == '\u0A02' || c == '\u0A3C' || c == '\u0A3E' || c == '\u0A3F' || (c >= '\u0A40' && c <= '\u0A42') || + (c >= '\u0A47' && c <= '\u0A48') || (c >= '\u0A4B' && c <= '\u0A4D') || (c >= '\u0A70' && c <= '\u0A71') || + (c >= '\u0A81' && c <= '\u0A83') || c == '\u0ABC' || (c >= '\u0ABE' && c <= '\u0AC5') || (c >= '\u0AC7' && c <= '\u0AC9') || + (c >= '\u0ACB' && c <= '\u0ACD') || (c >= '\u0B01' && c <= '\u0B03') || c == '\u0B3C' || (c >= '\u0B3E' && c <= '\u0B43') || + (c >= '\u0B47' && c <= '\u0B48') || (c >= '\u0B4B' && c <= '\u0B4D') || (c >= '\u0B56' && c <= '\u0B57') || + (c >= '\u0B82' && c <= '\u0B83') || (c >= '\u0BBE' && c <= '\u0BC2') || (c >= '\u0BC6' && c <= '\u0BC8') || + (c >= '\u0BCA' && c <= '\u0BCD') || c == '\u0BD7' || (c >= '\u0C01' && c <= '\u0C03') || (c >= '\u0C3E' && c <= '\u0C44') || + (c >= '\u0C46' && c <= '\u0C48') || (c >= '\u0C4A' && c <= '\u0C4D') || (c >= '\u0C55' && c <= '\u0C56') || + (c >= '\u0C82' && c <= '\u0C83') || (c >= '\u0CBE' && c <= '\u0CC4') || (c >= '\u0CC6' && c <= '\u0CC8') || + (c >= '\u0CCA' && c <= '\u0CCD') || (c >= '\u0CD5' && c <= '\u0CD6') || (c >= '\u0D02' && c <= '\u0D03') || + (c >= '\u0D3E' && c <= '\u0D43') || (c >= '\u0D46' && c <= '\u0D48') || (c >= '\u0D4A' && c <= '\u0D4D') || c == '\u0D57' || + c == '\u0E31' || (c >= '\u0E34' && c <= '\u0E3A') || (c >= '\u0E47' && c <= '\u0E4E') || c == '\u0EB1' || + (c >= '\u0EB4' && c <= '\u0EB9') || (c >= '\u0EBB' && c <= '\u0EBC') || (c >= '\u0EC8' && c <= '\u0ECD') || + (c >= '\u0F18' && c <= '\u0F19') || c == '\u0F35' || c == '\u0F37' || c == '\u0F39' || c == '\u0F3E' || c == '\u0F3F' || + (c >= '\u0F71' && c <= '\u0F84') || (c >= '\u0F86' && c <= '\u0F8B') || (c >= '\u0F90' && c <= '\u0F95') || c == '\u0F97' || + (c >= '\u0F99' && c <= '\u0FAD') || (c >= '\u0FB1' && c <= '\u0FB7') || c == '\u0FB9' || (c >= '\u20D0' && c <= '\u20DC') || + c == '\u20E1' || (c >= '\u302A' && c <= '\u302F') || c == '\u3099' || c == '\u309A'; + } + + private static boolean isExtender(char c) { + return c == '\u00B7' || c == '\u02D0' || c == '\u02D1' || c == '\u0387' || c == '\u0640' || c == '\u0E46' || + c == '\u0EC6' || c == '\u3005' || (c >= '\u3031' && c <= '\u3035') || (c >= '\u309D' && c <= '\u309E') || + (c >= '\u30FC' && c <= '\u30FE'); + } + + private static boolean isLetter(char c) { + return isBaseChar(c) || isIdeographic(c); + } + + private static boolean isBaseChar(char c) { + return (c >= '\u0041' && c <= '\u005A') || (c >= '\u0061' && c <= '\u007A') || (c >= '\u00C0' && c <= '\u00D6') || + (c >= '\u00D8' && c <= '\u00F6') || (c >= '\u00F8' && c <= '\u00FF') || (c >= '\u0100' && c <= '\u0131') || + (c >= '\u0134' && c <= '\u013E') || (c >= '\u0141' && c <= '\u0148') || (c >= '\u014A' && c <= '\u017E') || + (c >= '\u0180' && c <= '\u01C3') || (c >= '\u01CD' && c <= '\u01F0') || (c >= '\u01F4' && c <= '\u01F5') || + (c >= '\u01FA' && c <= '\u0217') || (c >= '\u0250' && c <= '\u02A8') || (c >= '\u02BB' && c <= '\u02C1') || + c == '\u0386' || (c >= '\u0388' && c <= '\u038A') || c == '\u038C' || (c >= '\u038E' && c <= '\u03A1') || + (c >= '\u03A3' && c <= '\u03CE') || (c >= '\u03D0' && c <= '\u03D6') || c == '\u03DA' || c == '\u03DC' || c == '\u03DE' || + c == '\u03E0' || (c >= '\u03E2' && c <= '\u03F3') || (c >= '\u0401' && c <= '\u040C') || (c >= '\u040E' && c <= '\u044F') || + (c >= '\u0451' && c <= '\u045C') || (c >= '\u045E' && c <= '\u0481') || (c >= '\u0490' && c <= '\u04C4') || + (c >= '\u04C7' && c <= '\u04C8') || (c >= '\u04CB' && c <= '\u04CC') || (c >= '\u04D0' && c <= '\u04EB') || + (c >= '\u04EE' && c <= '\u04F5') || (c >= '\u04F8' && c <= '\u04F9') || (c >= '\u0531' && c <= '\u0556') || + c == '\u0559' || (c >= '\u0561' && c <= '\u0586') || (c >= '\u05D0' && c <= '\u05EA') || (c >= '\u05F0' && c <= '\u05F2') || + (c >= '\u0621' && c <= '\u063A') || (c >= '\u0641' && c <= '\u064A') || (c >= '\u0671' && c <= '\u06B7') || + (c >= '\u06BA' && c <= '\u06BE') || (c >= '\u06C0' && c <= '\u06CE') || (c >= '\u06D0' && c <= '\u06D3') || + c == '\u06D5' || (c >= '\u06E5' && c <= '\u06E6') || (c >= '\u0905' && c <= '\u0939') || c == '\u093D' || + (c >= '\u0958' && c <= '\u0961') || (c >= '\u0985' && c <= '\u098C') || (c >= '\u098F' && c <= '\u0990') || + (c >= '\u0993' && c <= '\u09A8') || (c >= '\u09AA' && c <= '\u09B0') || c == '\u09B2' || + (c >= '\u09B6' && c <= '\u09B9') || (c >= '\u09DC' && c <= '\u09DD') || (c >= '\u09DF' && c <= '\u09E1') || + (c >= '\u09F0' && c <= '\u09F1') || (c >= '\u0A05' && c <= '\u0A0A') || (c >= '\u0A0F' && c <= '\u0A10') || + (c >= '\u0A13' && c <= '\u0A28') || (c >= '\u0A2A' && c <= '\u0A30') || (c >= '\u0A32' && c <= '\u0A33') || + (c >= '\u0A35' && c <= '\u0A36') || (c >= '\u0A38' && c <= '\u0A39') || (c >= '\u0A59' && c <= '\u0A5C') || + c == '\u0A5E' || (c >= '\u0A72' && c <= '\u0A74') || (c >= '\u0A85' && c <= '\u0A8B') || c == '\u0A8D' || + (c >= '\u0A8F' && c <= '\u0A91') || (c >= '\u0A93' && c <= '\u0AA8') || (c >= '\u0AAA' && c <= '\u0AB0') || + (c >= '\u0AB2' && c <= '\u0AB3') || (c >= '\u0AB5' && c <= '\u0AB9') || c == '\u0ABD' || c == '\u0AE0' || + (c >= '\u0B05' && c <= '\u0B0C') || (c >= '\u0B0F' && c <= '\u0B10') || (c >= '\u0B13' && c <= '\u0B28') || + (c >= '\u0B2A' && c <= '\u0B30') || (c >= '\u0B32' && c <= '\u0B33') || (c >= '\u0B36' && c <= '\u0B39') || + c == '\u0B3D' || (c >= '\u0B5C' && c <= '\u0B5D') || (c >= '\u0B5F' && c <= '\u0B61') || + (c >= '\u0B85' && c <= '\u0B8A') || (c >= '\u0B8E' && c <= '\u0B90') || (c >= '\u0B92' && c <= '\u0B95') || + (c >= '\u0B99' && c <= '\u0B9A') || c == '\u0B9C' || (c >= '\u0B9E' && c <= '\u0B9F') || + (c >= '\u0BA3' && c <= '\u0BA4') || (c >= '\u0BA8' && c <= '\u0BAA') || (c >= '\u0BAE' && c <= '\u0BB5') || + (c >= '\u0BB7' && c <= '\u0BB9') || (c >= '\u0C05' && c <= '\u0C0C') || (c >= '\u0C0E' && c <= '\u0C10') || + (c >= '\u0C12' && c <= '\u0C28') || (c >= '\u0C2A' && c <= '\u0C33') || (c >= '\u0C35' && c <= '\u0C39') || + (c >= '\u0C60' && c <= '\u0C61') || (c >= '\u0C85' && c <= '\u0C8C') || (c >= '\u0C8E' && c <= '\u0C90') || + (c >= '\u0C92' && c <= '\u0CA8') || (c >= '\u0CAA' && c <= '\u0CB3') || (c >= '\u0CB5' && c <= '\u0CB9') || + c == '\u0CDE' || (c >= '\u0CE0' && c <= '\u0CE1') || (c >= '\u0D05' && c <= '\u0D0C') || + (c >= '\u0D0E' && c <= '\u0D10') || (c >= '\u0D12' && c <= '\u0D28') || (c >= '\u0D2A' && c <= '\u0D39') || + (c >= '\u0D60' && c <= '\u0D61') || (c >= '\u0E01' && c <= '\u0E2E') || c == '\u0E30' || + (c >= '\u0E32' && c <= '\u0E33') || (c >= '\u0E40' && c <= '\u0E45') || (c >= '\u0E81' && c <= '\u0E82') || + c == '\u0E84' || (c >= '\u0E87' && c <= '\u0E88') || c == '\u0E8A' || c == '\u0E8D' || (c >= '\u0E94' && c <= '\u0E97') || + (c >= '\u0E99' && c <= '\u0E9F') || (c >= '\u0EA1' && c <= '\u0EA3') || c == '\u0EA5' || c == '\u0EA7' || + (c >= '\u0EAA' && c <= '\u0EAB') || (c >= '\u0EAD' && c <= '\u0EAE') || c == '\u0EB0' || + (c >= '\u0EB2' && c <= '\u0EB3') || c == '\u0EBD' || (c >= '\u0EC0' && c <= '\u0EC4') || + (c >= '\u0F40' && c <= '\u0F47') || (c >= '\u0F49' && c <= '\u0F69') || (c >= '\u10A0' && c <= '\u10C5') || + (c >= '\u10D0' && c <= '\u10F6') || c == '\u1100' || (c >= '\u1102' && c <= '\u1103') || + (c >= '\u1105' && c <= '\u1107') || c == '\u1109' || (c >= '\u110B' && c <= '\u110C') || + (c >= '\u110E' && c <= '\u1112') || c == '\u113C' || c == '\u113E' || c == '\u1140' || c == '\u114C' || + c == '\u114E' || c == '\u1150' || (c >= '\u1154' && c <= '\u1155') || c == '\u1159' || + (c >= '\u115F' && c <= '\u1161') || c == '\u1163' || c == '\u1165' || c == '\u1167' || c == '\u1169' || + (c >= '\u116D' && c <= '\u116E') || (c >= '\u1172' && c <= '\u1173') || c == '\u1175' || + c == '\u119E' || c == '\u11A8' || c == '\u11AB' || (c >= '\u11AE' && c <= '\u11AF') || + (c >= '\u11B7' && c <= '\u11B8') || c == '\u11BA' || (c >= '\u11BC' && c <= '\u11C2') || + c == '\u11EB' || c == '\u11F0' || c == '\u11F9' || (c >= '\u1E00' && c <= '\u1E9B') || (c >= '\u1EA0' && c <= '\u1EF9') || + (c >= '\u1F00' && c <= '\u1F15') || (c >= '\u1F18' && c <= '\u1F1D') || (c >= '\u1F20' && c <= '\u1F45') || + (c >= '\u1F48' && c <= '\u1F4D') || (c >= '\u1F50' && c <= '\u1F57') || c == '\u1F59' || c == '\u1F5B' || c == '\u1F5D' || + (c >= '\u1F5F' && c <= '\u1F7D') || (c >= '\u1F80' && c <= '\u1FB4') || (c >= '\u1FB6' && c <= '\u1FBC') || + c == '\u1FBE' || (c >= '\u1FC2' && c <= '\u1FC4') || (c >= '\u1FC6' && c <= '\u1FCC') || + (c >= '\u1FD0' && c <= '\u1FD3') || (c >= '\u1FD6' && c <= '\u1FDB') || (c >= '\u1FE0' && c <= '\u1FEC') || + (c >= '\u1FF2' && c <= '\u1FF4') || (c >= '\u1FF6' && c <= '\u1FFC') || c == '\u2126' || + (c >= '\u212A' && c <= '\u212B') || c == '\u212E' || (c >= '\u2180' && c <= '\u2182') || + (c >= '\u3041' && c <= '\u3094') || (c >= '\u30A1' && c <= '\u30FA') || (c >= '\u3105' && c <= '\u312C') || + (c >= '\uAC00' && c <= '\uD7A3'); + } + + private static boolean isIdeographic(char c) { + return (c >= '\u4E00' && c <= '\u9FA5') || c == '\u3007' || (c >= '\u3021' && c <= '\u3029'); + } + + public static String determineEncoding(InputStream stream) throws IOException { + stream.mark(20000); + try { + int b0 = stream.read(); + int b1 = stream.read(); + int b2 = stream.read(); + int b3 = stream.read(); + + if (b0 == 0xFE && b1 == 0xFF) + return "UTF-16BE"; + else if (b0 == 0xFF && b1 == 0xFE) + return "UTF-16LE"; + else if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF ) + return "UTF-8"; + else if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) + return "UTF-16BE"; + else if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) + return "UTF-16LE"; + else if (b0 == 0x3C && b1 == 0x3F && b2 == 0x78 && b3 == 0x6D) { +// UTF-8, ISO 646, ASCII, some part of ISO 8859, Shift-JIS, EUC, or any other 7-bit, 8-bit, or mixed-width encoding +// which ensures that the characters of ASCII have their normal positions, width, and values; the actual encoding +// declaration must be read to detect which of these applies, but since all of these encodings use the same bit patterns +// for the relevant ASCII characters, the encoding declaration itself may be read reliably + InputStreamReader rdr = new InputStreamReader(stream, "US-ASCII"); + String hdr = readFirstLine(rdr); + return extractEncoding(hdr); + } else + return null; + } finally { + stream.reset(); + } + } + + private static String extractEncoding(String hdr) { + int i = hdr.indexOf("encoding="); + if (i == -1) + return null; + hdr = hdr.substring(i+9); + char sep = hdr.charAt(0); + hdr = hdr.substring(1); + i = hdr.indexOf(sep); + if (i == -1) + return null; + return hdr.substring(0, i); + } + + private static String readFirstLine(InputStreamReader rdr) throws IOException { + char[] buf = new char[1]; + StringBuffer bldr = new StringBuffer(); + rdr.read(buf); + while (buf[0] != '>') { + bldr.append(buf[0]); + rdr.read(buf); + } + return bldr.toString(); + } + + + public static boolean charSetImpliesAscii(String charset) { + return charset.equals("ISO-8859-1") || charset.equals("US-ASCII"); + } + + + /** + * Converts the raw characters to XML escapeUrlParam characters. + * + * @param rawContent + * @param charset Null when charset is not known, so we assume it's unicode + * @param isNoLines + * @return escapeUrlParam string + */ + public static String escapeXML(String rawContent, String charset, boolean isNoLines) { + if (rawContent == null) + return ""; + else { + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < rawContent.length(); i++) { + char ch = rawContent.charAt(i); + if (ch == '\'') + sb.append("'"); + else if (ch == '&') + sb.append("&"); + else if (ch == '"') + sb.append("""); + else if (ch == '<') + sb.append("<"); + else if (ch == '>') + sb.append(">"); + else if (ch > '~' && charset != null && charSetImpliesAscii(charset)) + // TODO - why is hashcode the only way to get the unicode number for the character + // in jre 5.0? + sb.append("&#x"+Integer.toHexString(new Character(ch).hashCode()).toUpperCase()+";"); + else if (isNoLines) { + if (ch == '\r') + sb.append(" "); + else if (ch != '\n') + sb.append(ch); + } + else + sb.append(ch); + } + return sb.toString(); + } + } + + public static Element getFirstChild(Element e) { + if (e == null) + return null; + Node n = e.getFirstChild(); + while (n != null && n.getNodeType() != Node.ELEMENT_NODE) + n = n.getNextSibling(); + return (Element) n; + } + + public static Element getNamedChild(Element e, String name) { + Element c = getFirstChild(e); + while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName())) + c = getNextSibling(c); + return c; + } + + public static Element getNextSibling(Element e) { + Node n = e.getNextSibling(); + while (n != null && n.getNodeType() != Node.ELEMENT_NODE) + n = n.getNextSibling(); + return (Element) n; + } + + public static void getNamedChildren(Element e, String name, List set) { + Element c = getFirstChild(e); + while (c != null) { + if (name.equals(c.getLocalName()) || name.equals(c.getNodeName()) ) + set.add(c); + c = getNextSibling(c); + } + } + + public static String htmlToXmlEscapedPlainText(Element r) { + StringBuilder s = new StringBuilder(); + Node n = r.getFirstChild(); + boolean ws = false; + while (n != null) { + if (n.getNodeType() == Node.TEXT_NODE) { + String t = n.getTextContent().trim(); + if (Utilities.noString(t)) + ws = true; + else { + if (ws) + s.append(" "); + ws = false; + s.append(t); + } + } + if (n.getNodeType() == Node.ELEMENT_NODE) { + if (ws) + s.append(" "); + ws = false; + s.append(htmlToXmlEscapedPlainText((Element) n)); + if (r.getNodeName().equals("br") || r.getNodeName().equals("p")) + s.append("\r\n"); + } + n = n.getNextSibling(); + } + return s.toString(); + } + + public static String htmlToXmlEscapedPlainText(String definition) throws Exception { + return htmlToXmlEscapedPlainText(parseToDom("
    "+definition+"
    ").getDocumentElement()); + } + + public static String elementToString(Element el) { + if (el == null) + return ""; + Document document = el.getOwnerDocument(); + DOMImplementationLS domImplLS = (DOMImplementationLS) document + .getImplementation(); + LSSerializer serializer = domImplLS.createLSSerializer(); + return serializer.writeToString(el); + } + + public static String getNamedChildValue(Element element, String name) { + Element e = getNamedChild(element, name); + return e == null ? null : e.getAttribute("value"); + } + + public static void getNamedChildrenWithWildcard(Element focus, String name, List children) { + Element c = getFirstChild(focus); + while (c != null) { + String n = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); + if (name.equals(n) || (name.endsWith("[x]") && n.startsWith(name.substring(0, name.length()-3)))) + children.add(c); + c = getNextSibling(c); + } + + } + + public static boolean hasNamedChild(Element e, String name) { + Element c = getFirstChild(e); + while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName())) + c = getNextSibling(c); + return c != null; + } + + public static Document parseToDom(String content) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(new ByteArrayInputStream(content.getBytes())); + } + + public static Element getLastChild(Element e) { + if (e == null) + return null; + Node n = e.getLastChild(); + while (n != null && n.getNodeType() != Node.ELEMENT_NODE) + n = n.getPreviousSibling(); + return (Element) n; + } + + public static Element getPrevSibling(Element e) { + Node n = e.getPreviousSibling(); + while (n != null && n.getNodeType() != Node.ELEMENT_NODE) + n = n.getPreviousSibling(); + return (Element) n; + } + + +} diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/elementmodel/VerticalBarParser.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/elementmodel/VerticalBarParser.java index 40181ec4472..45e39593a35 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/elementmodel/VerticalBarParser.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/elementmodel/VerticalBarParser.java @@ -1,493 +1,493 @@ -package org.hl7.fhir.r4.elementmodel; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; - -import org.hl7.fhir.r4.context.IWorkerContext; -import org.hl7.fhir.r4.formats.IParser.OutputStyle; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.exceptions.DefinitionException; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.FHIRFormatError; - -/** - * This class provides special support for parsing v2 by the v2 logical model - * For the logical model, see the FHIRPath spec - * - * @author Grahame Grieve - * - */ -public class VerticalBarParser extends ParserBase { - - /** - * Delimiters for a message. Note that the application rarely needs to concern - * itself with this information; it mainly exists for internal use. However if - * a message is being written to a spec that calls for non-standard delimiters, - * the application can set them here. - * - * @author Grahame - * - */ - public class Delimiters { - - /** - * Hl7 defined default delimiter for a field - */ - public final static char DEFAULT_DELIMITER_FIELD = '|'; - - /** - * Hl7 defined default delimiter for a component - */ - public final static char DEFAULT_DELIMITER_COMPONENT = '^'; - - /** - * Hl7 defined default delimiter for a subcomponent - */ - public final static char DEFAULT_DELIMITER_SUBCOMPONENT = '&'; - - /** - * Hl7 defined default delimiter for a repeat - */ - public final static char DEFAULT_DELIMITER_REPETITION = '~'; - - /** - * Hl7 defined default delimiter for an escape - */ - public final static char DEFAULT_CHARACTER_ESCAPE = '\\'; - - - /** - * defined escape character for this message - */ - private char escapeCharacter; - - /** - * defined repetition character for this message - */ - private char repetitionDelimiter; - - /** - * defined field character for this message - */ - private char fieldDelimiter; - - /** - * defined subComponent character for this message - */ - private char subComponentDelimiter; - - /** - * defined component character for this message - */ - private char componentDelimiter; - - /** - * create - * - */ - public Delimiters() { - super(); - reset(); - } - - public boolean matches(Delimiters other) { - return escapeCharacter == other.escapeCharacter && - repetitionDelimiter == other.repetitionDelimiter && - fieldDelimiter == other.fieldDelimiter && - subComponentDelimiter == other.subComponentDelimiter && - componentDelimiter == other.componentDelimiter; - } - - /** - * get defined component character for this message - * @return - */ - public char getComponentDelimiter() { - return componentDelimiter; - } - - /** - * set defined component character for this message - * @param componentDelimiter - */ - public void setComponentDelimiter(char componentDelimiter) { - this.componentDelimiter = componentDelimiter; - } - - /** - * get defined escape character for this message - * @return - */ - public char getEscapeCharacter() { - return escapeCharacter; - } - - /** - * set defined escape character for this message - * @param escapeCharacter - */ - public void setEscapeCharacter(char escapeCharacter) { - this.escapeCharacter = escapeCharacter; - } - - /** - * get defined field character for this message - * @return - */ - public char getFieldDelimiter() { - return fieldDelimiter; - } - - /** - * set defined field character for this message - * @param fieldDelimiter - */ - public void setFieldDelimiter(char fieldDelimiter) { - this.fieldDelimiter = fieldDelimiter; - } - - /** - * get repeat field character for this message - * @return - */ - public char getRepetitionDelimiter() { - return repetitionDelimiter; - } - - /** - * set repeat field character for this message - * @param repetitionDelimiter - */ - public void setRepetitionDelimiter(char repetitionDelimiter) { - this.repetitionDelimiter = repetitionDelimiter; - } - - /** - * get sub-component field character for this message - * @return - */ - public char getSubComponentDelimiter() { - return subComponentDelimiter; - } - - /** - * set sub-component field character for this message - * @param subComponentDelimiter - */ - public void setSubComponentDelimiter(char subComponentDelimiter) { - this.subComponentDelimiter = subComponentDelimiter; - } - - /** - * reset to default HL7 values - * - */ - public void reset () { - fieldDelimiter = DEFAULT_DELIMITER_FIELD; - componentDelimiter = DEFAULT_DELIMITER_COMPONENT; - subComponentDelimiter = DEFAULT_DELIMITER_SUBCOMPONENT; - repetitionDelimiter = DEFAULT_DELIMITER_REPETITION; - escapeCharacter = DEFAULT_CHARACTER_ESCAPE; - } - - /** - * check that the delimiters are valid - * - * @throws FHIRException - */ - public void check() throws FHIRException { - rule(componentDelimiter != fieldDelimiter, "Delimiter Error: \""+componentDelimiter+"\" is used for both CPComponent and CPField"); - rule(subComponentDelimiter != fieldDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPField"); - rule(subComponentDelimiter != componentDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPComponent"); - rule(repetitionDelimiter != fieldDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPField"); - rule(repetitionDelimiter != componentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPComponent"); - rule(repetitionDelimiter != subComponentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPSubComponent"); - rule(escapeCharacter != fieldDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPField"); - rule(escapeCharacter != componentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPComponent"); - rule(escapeCharacter != subComponentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPSubComponent"); - rule(escapeCharacter != repetitionDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and Repetition"); - } - - /** - * check to see whether ch is a delimiter character (vertical bar parser support) - * @param ch - * @return - */ - public boolean isDelimiter(char ch) { - return ch == escapeCharacter || ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter || ch == componentDelimiter; - } - - /** - * check to see whether ch is a cell delimiter char (vertical bar parser support) - * @param ch - * @return - */ - public boolean isCellDelimiter(char ch) { - return ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter || ch == componentDelimiter; - } - - /** - * get the escape for a character - * @param ch - * @return - */ - public String getEscape(char ch) { - if (ch == escapeCharacter) - return escapeCharacter + "E" + escapeCharacter; - else if (ch == fieldDelimiter) - return escapeCharacter + "F" + escapeCharacter; - else if (ch == componentDelimiter) - return escapeCharacter + "S" + escapeCharacter; - else if (ch == subComponentDelimiter) - return escapeCharacter + "T" + escapeCharacter; - else if (ch == repetitionDelimiter) - return escapeCharacter + "R" + escapeCharacter; - else - return null; - } - - /** - * build the MSH-2 content - * @return - */ - public String forMSH2() { - return "" + componentDelimiter + repetitionDelimiter + escapeCharacter + subComponentDelimiter; - } - - /** - * check to see whether ch represents a delimiter escape - * @param ch - * @return - */ - public boolean isDelimiterEscape(char ch) { - return ch == 'F' || ch == 'S' || ch == 'E' || ch == 'T' || ch == 'R'; - } - - /** - * get escape for ch in an escape - * @param ch - * @return - * @throws DefinitionException - * @throws FHIRException - */ - public char getDelimiterEscapeChar(char ch) throws DefinitionException { - if (ch == 'E') - return escapeCharacter; - else if (ch == 'F') - return fieldDelimiter; - else if (ch == 'S') - return componentDelimiter; - else if (ch == 'T') - return subComponentDelimiter; - else if (ch == 'R') - return repetitionDelimiter; - else - throw new DefinitionException("internal error in getDelimiterEscapeChar"); - } - } - - public class VerticalBarParserReader { - - - private BufferedInputStream stream; - private String charsetName; - private InputStreamReader reader = null; - private boolean finished; - private char peeked; - private char lastValue; - private int offset; - private int lineNumber; - - public VerticalBarParserReader(BufferedInputStream stream, String charsetName) throws FHIRException { - super(); - setStream(stream); - setCharsetName(charsetName); - open(); - } - - public String getCharsetName() { - return charsetName; - } - - public void setCharsetName(String charsetName) { - this.charsetName = charsetName; - } - - public BufferedInputStream getStream() { - return stream; - } - - public void setStream(BufferedInputStream stream) { - this.stream = stream; - } - - private void open() throws FHIRException { - try { - stream.mark(2048); - reader = new InputStreamReader(stream, charsetName); - offset = 0; - lineNumber = 0; - lastValue = ' '; - next(); - } catch (Exception e) { - throw new FHIRException(e); - } - } - - private void next() throws IOException, FHIRException { - finished = !reader.ready(); - if (!finished) { - char[] temp = new char[1]; - rule(reader.read(temp, 0, 1) == 1, "unable to read 1 character from the stream"); - peeked = temp[0]; - } - } - - public String read(int charCount) throws FHIRException { - String value = ""; - for (int i = 0; i < charCount; i++) - value = value + read(); - return value; - } - - public void skipEOL () throws FHIRException { - while (!finished && (peek() == '\r' || peek() == '\n')) - read(); - } - - public char read () throws FHIRException { - rule(!finished, "No more content to read"); - char value = peek(); - offset++; - if (value == '\r' || value == '\n') { - if (lastValue != '\r' || value != '\n') - lineNumber++; - } - lastValue = value; - try { - next(); - } catch (Exception e) { - throw new FHIRException(e); - } - return value; - } - - public boolean isFinished () { - return finished; - } - - public char peek() throws FHIRException { - rule(!finished, "Cannot peek"); - return peeked; - } - - public void mark() { - stream.mark(2048); - } - - public void reset() throws FHIRException { - try { - stream.reset(); - } catch (IOException e) { - throw new FHIRException(e); - } - open(); - } - - public boolean IsEOL() throws FHIRException { - return peek() == '\r' || peek() == '\n'; - } - - public int getLineNumber() { - return lineNumber; - } - - public int getOffset() { - return offset; - } - - } - - public VerticalBarParser(IWorkerContext context) { - super(context); - } - - private String charset = "ASCII"; - private Delimiters delimiters = new Delimiters(); - - @Override - public Element parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException { - StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/v2/StructureDefinition/Message"); - Element message = new Element("Message", new Property(context, sd.getSnapshot().getElementFirstRep(), sd)); - VerticalBarParserReader reader = new VerticalBarParserReader(new BufferedInputStream(stream), charset); - - preDecode(reader); - while (!reader.isFinished()) // && (getOptions().getSegmentLimit() == 0 || getOptions().getSegmentLimit() > message.getSegments().size())) - readSegment(message, reader); - - return message; - } - - private void preDecode(VerticalBarParserReader reader) throws FHIRException { - reader.skipEOL(); - String temp = reader.read(3); - rule(temp.equals("MSH") || temp.equals("FHS"), "Found '" + temp + "' looking for 'MSH' or 'FHS'"); - readDelimiters(reader); - // readVersion(message); - probably don't need to do that? - // readCharacterSet(); - reader.reset(); // ready to read message now - } - - private void rule(boolean test, String msg) throws FHIRException { - if (!test) - throw new FHIRException(msg); - } - - private void readDelimiters(VerticalBarParserReader reader) throws FHIRException { - delimiters.setFieldDelimiter(reader.read()); - if (!(reader.peek() == delimiters.getFieldDelimiter())) - delimiters.setComponentDelimiter(reader.read()); - if (!(reader.peek() == delimiters.getFieldDelimiter())) - delimiters.setRepetitionDelimiter(reader.read()); - if (!(reader.peek() == delimiters.getFieldDelimiter())) - delimiters.setEscapeCharacter(reader.read()); - if (!(reader.peek() == delimiters.getFieldDelimiter())) - delimiters.setSubComponentDelimiter(reader.read()); - delimiters.check(); - } - - private void readSegment(Element message, VerticalBarParserReader reader) throws FHIRException { - Element segment = new Element("segment", message.getProperty().getChild("segment")); - message.getChildren().add(segment); - Element segmentCode = new Element("code", segment.getProperty().getChild("code")); - segment.getChildren().add(segmentCode); - segmentCode.setValue(reader.read(3)); - - int index = 0; - while (!reader.isFinished() && !reader.IsEOL()) { - index++; - readField(reader, segment, index); - if (!reader.isFinished() && !reader.IsEOL()) - rule(reader.read() == delimiters.getFieldDelimiter(), "Expected to find field delimiter"); - } - if (!reader.isFinished()) - reader.skipEOL(); - } - - - - private void readField(VerticalBarParserReader reader, Element segment, int index) { - // TODO Auto-generated method stub - - } - - @Override - public void compose(Element e, OutputStream destination, OutputStyle style, String base) { - // TODO Auto-generated method stub - - } - -} +package org.hl7.fhir.r4.elementmodel; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; + +import org.hl7.fhir.r4.context.IWorkerContext; +import org.hl7.fhir.r4.formats.IParser.OutputStyle; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; + +/** + * This class provides special support for parsing v2 by the v2 logical model + * For the logical model, see the FHIRPath spec + * + * @author Grahame Grieve + * + */ +public class VerticalBarParser extends ParserBase { + + /** + * Delimiters for a message. Note that the application rarely needs to concern + * itself with this information; it mainly exists for internal use. However if + * a message is being written to a spec that calls for non-standard delimiters, + * the application can set them here. + * + * @author Grahame + * + */ + public class Delimiters { + + /** + * Hl7 defined default delimiter for a field + */ + public final static char DEFAULT_DELIMITER_FIELD = '|'; + + /** + * Hl7 defined default delimiter for a component + */ + public final static char DEFAULT_DELIMITER_COMPONENT = '^'; + + /** + * Hl7 defined default delimiter for a subcomponent + */ + public final static char DEFAULT_DELIMITER_SUBCOMPONENT = '&'; + + /** + * Hl7 defined default delimiter for a repeat + */ + public final static char DEFAULT_DELIMITER_REPETITION = '~'; + + /** + * Hl7 defined default delimiter for an escapeUrlParam + */ + public final static char DEFAULT_CHARACTER_ESCAPE = '\\'; + + + /** + * defined escapeUrlParam character for this message + */ + private char escapeCharacter; + + /** + * defined repetition character for this message + */ + private char repetitionDelimiter; + + /** + * defined field character for this message + */ + private char fieldDelimiter; + + /** + * defined subComponent character for this message + */ + private char subComponentDelimiter; + + /** + * defined component character for this message + */ + private char componentDelimiter; + + /** + * create + * + */ + public Delimiters() { + super(); + reset(); + } + + public boolean matches(Delimiters other) { + return escapeCharacter == other.escapeCharacter && + repetitionDelimiter == other.repetitionDelimiter && + fieldDelimiter == other.fieldDelimiter && + subComponentDelimiter == other.subComponentDelimiter && + componentDelimiter == other.componentDelimiter; + } + + /** + * get defined component character for this message + * @return + */ + public char getComponentDelimiter() { + return componentDelimiter; + } + + /** + * set defined component character for this message + * @param componentDelimiter + */ + public void setComponentDelimiter(char componentDelimiter) { + this.componentDelimiter = componentDelimiter; + } + + /** + * get defined escapeUrlParam character for this message + * @return + */ + public char getEscapeCharacter() { + return escapeCharacter; + } + + /** + * set defined escapeUrlParam character for this message + * @param escapeCharacter + */ + public void setEscapeCharacter(char escapeCharacter) { + this.escapeCharacter = escapeCharacter; + } + + /** + * get defined field character for this message + * @return + */ + public char getFieldDelimiter() { + return fieldDelimiter; + } + + /** + * set defined field character for this message + * @param fieldDelimiter + */ + public void setFieldDelimiter(char fieldDelimiter) { + this.fieldDelimiter = fieldDelimiter; + } + + /** + * get repeat field character for this message + * @return + */ + public char getRepetitionDelimiter() { + return repetitionDelimiter; + } + + /** + * set repeat field character for this message + * @param repetitionDelimiter + */ + public void setRepetitionDelimiter(char repetitionDelimiter) { + this.repetitionDelimiter = repetitionDelimiter; + } + + /** + * get sub-component field character for this message + * @return + */ + public char getSubComponentDelimiter() { + return subComponentDelimiter; + } + + /** + * set sub-component field character for this message + * @param subComponentDelimiter + */ + public void setSubComponentDelimiter(char subComponentDelimiter) { + this.subComponentDelimiter = subComponentDelimiter; + } + + /** + * reset to default HL7 values + * + */ + public void reset () { + fieldDelimiter = DEFAULT_DELIMITER_FIELD; + componentDelimiter = DEFAULT_DELIMITER_COMPONENT; + subComponentDelimiter = DEFAULT_DELIMITER_SUBCOMPONENT; + repetitionDelimiter = DEFAULT_DELIMITER_REPETITION; + escapeCharacter = DEFAULT_CHARACTER_ESCAPE; + } + + /** + * check that the delimiters are valid + * + * @throws FHIRException + */ + public void check() throws FHIRException { + rule(componentDelimiter != fieldDelimiter, "Delimiter Error: \""+componentDelimiter+"\" is used for both CPComponent and CPField"); + rule(subComponentDelimiter != fieldDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPField"); + rule(subComponentDelimiter != componentDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPComponent"); + rule(repetitionDelimiter != fieldDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPField"); + rule(repetitionDelimiter != componentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPComponent"); + rule(repetitionDelimiter != subComponentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPSubComponent"); + rule(escapeCharacter != fieldDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPField"); + rule(escapeCharacter != componentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPComponent"); + rule(escapeCharacter != subComponentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPSubComponent"); + rule(escapeCharacter != repetitionDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and Repetition"); + } + + /** + * check to see whether ch is a delimiter character (vertical bar parser support) + * @param ch + * @return + */ + public boolean isDelimiter(char ch) { + return ch == escapeCharacter || ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter || ch == componentDelimiter; + } + + /** + * check to see whether ch is a cell delimiter char (vertical bar parser support) + * @param ch + * @return + */ + public boolean isCellDelimiter(char ch) { + return ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter || ch == componentDelimiter; + } + + /** + * get the escapeUrlParam for a character + * @param ch + * @return + */ + public String getEscape(char ch) { + if (ch == escapeCharacter) + return escapeCharacter + "E" + escapeCharacter; + else if (ch == fieldDelimiter) + return escapeCharacter + "F" + escapeCharacter; + else if (ch == componentDelimiter) + return escapeCharacter + "S" + escapeCharacter; + else if (ch == subComponentDelimiter) + return escapeCharacter + "T" + escapeCharacter; + else if (ch == repetitionDelimiter) + return escapeCharacter + "R" + escapeCharacter; + else + return null; + } + + /** + * build the MSH-2 content + * @return + */ + public String forMSH2() { + return "" + componentDelimiter + repetitionDelimiter + escapeCharacter + subComponentDelimiter; + } + + /** + * check to see whether ch represents a delimiter escapeUrlParam + * @param ch + * @return + */ + public boolean isDelimiterEscape(char ch) { + return ch == 'F' || ch == 'S' || ch == 'E' || ch == 'T' || ch == 'R'; + } + + /** + * get escapeUrlParam for ch in an escapeUrlParam + * @param ch + * @return + * @throws DefinitionException + * @throws FHIRException + */ + public char getDelimiterEscapeChar(char ch) throws DefinitionException { + if (ch == 'E') + return escapeCharacter; + else if (ch == 'F') + return fieldDelimiter; + else if (ch == 'S') + return componentDelimiter; + else if (ch == 'T') + return subComponentDelimiter; + else if (ch == 'R') + return repetitionDelimiter; + else + throw new DefinitionException("internal error in getDelimiterEscapeChar"); + } + } + + public class VerticalBarParserReader { + + + private BufferedInputStream stream; + private String charsetName; + private InputStreamReader reader = null; + private boolean finished; + private char peeked; + private char lastValue; + private int offset; + private int lineNumber; + + public VerticalBarParserReader(BufferedInputStream stream, String charsetName) throws FHIRException { + super(); + setStream(stream); + setCharsetName(charsetName); + open(); + } + + public String getCharsetName() { + return charsetName; + } + + public void setCharsetName(String charsetName) { + this.charsetName = charsetName; + } + + public BufferedInputStream getStream() { + return stream; + } + + public void setStream(BufferedInputStream stream) { + this.stream = stream; + } + + private void open() throws FHIRException { + try { + stream.mark(2048); + reader = new InputStreamReader(stream, charsetName); + offset = 0; + lineNumber = 0; + lastValue = ' '; + next(); + } catch (Exception e) { + throw new FHIRException(e); + } + } + + private void next() throws IOException, FHIRException { + finished = !reader.ready(); + if (!finished) { + char[] temp = new char[1]; + rule(reader.read(temp, 0, 1) == 1, "unable to read 1 character from the stream"); + peeked = temp[0]; + } + } + + public String read(int charCount) throws FHIRException { + String value = ""; + for (int i = 0; i < charCount; i++) + value = value + read(); + return value; + } + + public void skipEOL () throws FHIRException { + while (!finished && (peek() == '\r' || peek() == '\n')) + read(); + } + + public char read () throws FHIRException { + rule(!finished, "No more content to read"); + char value = peek(); + offset++; + if (value == '\r' || value == '\n') { + if (lastValue != '\r' || value != '\n') + lineNumber++; + } + lastValue = value; + try { + next(); + } catch (Exception e) { + throw new FHIRException(e); + } + return value; + } + + public boolean isFinished () { + return finished; + } + + public char peek() throws FHIRException { + rule(!finished, "Cannot peek"); + return peeked; + } + + public void mark() { + stream.mark(2048); + } + + public void reset() throws FHIRException { + try { + stream.reset(); + } catch (IOException e) { + throw new FHIRException(e); + } + open(); + } + + public boolean IsEOL() throws FHIRException { + return peek() == '\r' || peek() == '\n'; + } + + public int getLineNumber() { + return lineNumber; + } + + public int getOffset() { + return offset; + } + + } + + public VerticalBarParser(IWorkerContext context) { + super(context); + } + + private String charset = "ASCII"; + private Delimiters delimiters = new Delimiters(); + + @Override + public Element parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/v2/StructureDefinition/Message"); + Element message = new Element("Message", new Property(context, sd.getSnapshot().getElementFirstRep(), sd)); + VerticalBarParserReader reader = new VerticalBarParserReader(new BufferedInputStream(stream), charset); + + preDecode(reader); + while (!reader.isFinished()) // && (getOptions().getSegmentLimit() == 0 || getOptions().getSegmentLimit() > message.getSegments().size())) + readSegment(message, reader); + + return message; + } + + private void preDecode(VerticalBarParserReader reader) throws FHIRException { + reader.skipEOL(); + String temp = reader.read(3); + rule(temp.equals("MSH") || temp.equals("FHS"), "Found '" + temp + "' looking for 'MSH' or 'FHS'"); + readDelimiters(reader); + // readVersion(message); - probably don't need to do that? + // readCharacterSet(); + reader.reset(); // ready to read message now + } + + private void rule(boolean test, String msg) throws FHIRException { + if (!test) + throw new FHIRException(msg); + } + + private void readDelimiters(VerticalBarParserReader reader) throws FHIRException { + delimiters.setFieldDelimiter(reader.read()); + if (!(reader.peek() == delimiters.getFieldDelimiter())) + delimiters.setComponentDelimiter(reader.read()); + if (!(reader.peek() == delimiters.getFieldDelimiter())) + delimiters.setRepetitionDelimiter(reader.read()); + if (!(reader.peek() == delimiters.getFieldDelimiter())) + delimiters.setEscapeCharacter(reader.read()); + if (!(reader.peek() == delimiters.getFieldDelimiter())) + delimiters.setSubComponentDelimiter(reader.read()); + delimiters.check(); + } + + private void readSegment(Element message, VerticalBarParserReader reader) throws FHIRException { + Element segment = new Element("segment", message.getProperty().getChild("segment")); + message.getChildren().add(segment); + Element segmentCode = new Element("code", segment.getProperty().getChild("code")); + segment.getChildren().add(segmentCode); + segmentCode.setValue(reader.read(3)); + + int index = 0; + while (!reader.isFinished() && !reader.IsEOL()) { + index++; + readField(reader, segment, index); + if (!reader.isFinished() && !reader.IsEOL()) + rule(reader.read() == delimiters.getFieldDelimiter(), "Expected to find field delimiter"); + } + if (!reader.isFinished()) + reader.skipEOL(); + } + + + + private void readField(VerticalBarParserReader reader, Element segment, int index) { + // TODO Auto-generated method stub + + } + + @Override + public void compose(Element e, OutputStream destination, OutputStyle style, String base) { + // TODO Auto-generated method stub + + } + +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java index 97ee6c306e1..60c788985dd 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java @@ -1,21 +1,35 @@ package ca.uhn.fhir.rest.client; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.InputStream; -import java.io.StringReader; -import java.net.URLEncoder; -import java.nio.charset.Charset; -import java.util.*; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.api.annotation.ResourceDef; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.client.apache.ApacheHttpRequest; +import ca.uhn.fhir.rest.client.api.IBasicClient; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; +import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; +import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ReaderInputStream; -import org.apache.http.*; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.*; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicStatusLine; import org.hamcrest.core.StringContains; @@ -23,29 +37,23 @@ import org.hamcrest.core.StringEndsWith; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; -import org.junit.*; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import com.google.common.base.Charsets; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.*; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.api.annotation.ResourceDef; -import ca.uhn.fhir.model.base.resource.BaseConformance; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.client.apache.ApacheHttpRequest; -import ca.uhn.fhir.rest.client.api.IBasicClient; -import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; -import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; -import ca.uhn.fhir.rest.param.*; -import ca.uhn.fhir.rest.server.exceptions.*; -import ca.uhn.fhir.util.TestUtil; -import ca.uhn.fhir.util.UrlUtil; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ClientR4Test { @@ -569,7 +577,7 @@ public class ClientR4Test { DateParam date = new DateParam("2001-01-01"); client.getObservationByNameValueDate(new CompositeParam(str, date)); - assertEquals("http://foo/Observation?" + Observation.SP_CODE_VALUE_DATE + "=" + URLEncoder.encode("FOO\\$BAR$2001-01-01", "UTF-8"), capt.getValue().getURI().toString()); + assertEquals("http://foo/Observation?" + Observation.SP_CODE_VALUE_DATE + "=" + UrlUtil.escapeUrlParam("FOO\\$BAR$2001-01-01"), capt.getValue().getURI().toString()); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index 34579616a06..1565d6f4fcb 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -1,45 +1,6 @@ package ca.uhn.fhir.rest.client; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.*; -import java.net.URLEncoder; -import java.nio.charset.Charset; -import java.util.*; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.ReaderInputStream; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.*; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.*; -import org.apache.http.message.BasicHeader; -import org.apache.http.message.BasicStatusLine; -import org.hamcrest.Matchers; -import org.hamcrest.core.StringContains; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Bundle.BundleType; -import org.hl7.fhir.r4.model.Bundle.HTTPVerb; -import org.junit.*; -import org.mockito.ArgumentCaptor; -import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.client.api.IGenericClient; @@ -49,7 +10,43 @@ import ca.uhn.fhir.rest.client.impl.BaseClient; import ca.uhn.fhir.rest.client.impl.GenericClient; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.util.*; +import ca.uhn.fhir.util.BundleUtil; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicStatusLine; +import org.hamcrest.Matchers; +import org.hamcrest.core.StringContains; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.junit.*; +import org.mockito.ArgumentCaptor; +import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.Charset; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class GenericClientTest { @@ -782,7 +779,7 @@ public class GenericClientTest { .returnBundle(Bundle.class) .execute(); - assertEquals("http://foo/Observation?" + Observation.SP_CODE_VALUE_DATE + "=" + URLEncoder.encode("FOO\\$BAR$2001-01-01", "UTF-8"), capt.getValue().getURI().toString()); + assertEquals("http://foo/Observation?" + Observation.SP_CODE_VALUE_DATE + "=" + UrlUtil.escapeUrlParam("FOO\\$BAR$2001-01-01"), capt.getValue().getURI().toString()); } @@ -1025,7 +1022,7 @@ public class GenericClientTest { .returnBundle(Bundle.class) .execute(); - assertEquals("http://example.com/fhir/Patient?name=" + URLEncoder.encode("AAA,BBB,C\\,C", "UTF-8"), capt.getAllValues().get(1).getURI().toString()); + assertEquals("http://example.com/fhir/Patient?name=" + UrlUtil.escapeUrlParam("AAA,BBB,C\\,C"), capt.getAllValues().get(1).getURI().toString()); } @@ -1116,7 +1113,7 @@ public class GenericClientTest { .returnBundle(Bundle.class) .execute(); - assertEquals("http://example.com/fhir/Patient?identifier=" + URLEncoder.encode("A|B,C|D", "UTF-8"), capt.getAllValues().get(2).getURI().toString()); + assertEquals("http://example.com/fhir/Patient?identifier=" + UrlUtil.escapeUrlParam("A|B,C|D"), capt.getAllValues().get(2).getURI().toString()); } @@ -1152,7 +1149,7 @@ public class GenericClientTest { String url = capt.getAllValues().get(index).getURI().toString(); assertThat(url, Matchers.startsWith(wantPrefix)); assertEquals(wantValue, UrlUtil.unescape(url.substring(wantPrefix.length()))); - assertEquals(UrlUtil.escape(wantValue), url.substring(wantPrefix.length())); + assertEquals(UrlUtil.escapeUrlParam(wantValue), url.substring(wantPrefix.length())); index++; response = client.search() @@ -1164,7 +1161,7 @@ public class GenericClientTest { url = capt.getAllValues().get(index).getURI().toString(); assertThat(url, Matchers.startsWith(wantPrefix)); assertEquals(wantValue, UrlUtil.unescape(url.substring(wantPrefix.length()))); - assertEquals(UrlUtil.escape(wantValue), url.substring(wantPrefix.length())); + assertEquals(UrlUtil.escapeUrlParam(wantValue), url.substring(wantPrefix.length())); index++; } @@ -1234,8 +1231,8 @@ public class GenericClientTest { .execute(); assertThat(capt.getValue().getURI().toString(), containsString("http://example.com/fhir/Patient?")); - assertThat(capt.getValue().getURI().toString(), containsString("_include=" + UrlUtil.escape(Patient.INCLUDE_ORGANIZATION.getValue()))); - assertThat(capt.getValue().getURI().toString(), containsString("_include%3Arecurse=" + UrlUtil.escape(Patient.INCLUDE_LINK.getValue()))); + assertThat(capt.getValue().getURI().toString(), containsString("_include=" + UrlUtil.escapeUrlParam(Patient.INCLUDE_ORGANIZATION.getValue()))); + assertThat(capt.getValue().getURI().toString(), containsString("_include%3Arecurse=" + UrlUtil.escapeUrlParam(Patient.INCLUDE_LINK.getValue()))); assertThat(capt.getValue().getURI().toString(), containsString("_include=*")); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/GraphQLR4ProviderTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/GraphQLR4ProviderTest.java index 973835eb88f..ba8ff1d8796 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/GraphQLR4ProviderTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/GraphQLR4ProviderTest.java @@ -54,7 +54,7 @@ public class GraphQLR4ProviderTest { @Test public void testGraphInstance() throws Exception { String query = "{name{family,given}}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); @@ -80,7 +80,7 @@ public class GraphQLR4ProviderTest { @Test public void testGraphInstanceWithFhirpath() throws Exception { String query = "{name(fhirpath:\"family.exists()\"){text,given,family}}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); @@ -104,7 +104,7 @@ public class GraphQLR4ProviderTest { @Test public void testGraphSystemInstance() throws Exception { String query = "{Patient(id:123){id,name{given,family}}}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); @@ -132,7 +132,7 @@ public class GraphQLR4ProviderTest { @Test public void testGraphSystemList() throws Exception { String query = "{PatientList(name:\"pet\"){name{family,given}}}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/GraphQLR4RawTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/GraphQLR4RawTest.java index 372ee9d626e..1d3a9785efe 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/GraphQLR4RawTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/GraphQLR4RawTest.java @@ -91,7 +91,7 @@ public class GraphQLR4RawTest { ourNextRetVal = "{\"foo\"}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escape("{name{family,given}}")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam("{name{family,given}}")); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); @@ -114,7 +114,7 @@ public class GraphQLR4RawTest { ourNextRetVal = "{\"foo\"}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Condition/123/$graphql?query=" + UrlUtil.escape("{name{family,given}}")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Condition/123/$graphql?query=" + UrlUtil.escapeUrlParam("{name{family,given}}")); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); @@ -132,7 +132,7 @@ public class GraphQLR4RawTest { ourNextRetVal = "{\"foo\"}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escape("{name{family,given}}")); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam("{name{family,given}}")); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java index 86f43d9114c..73d0a03d23d 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java @@ -131,25 +131,25 @@ public class SearchR4Test { httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=" + UrlUtil.escape(Constants.CT_FHIR_JSON_NEW))); + assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); // Fetch the next page httpGet = new HttpGet(linkNext); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=" + UrlUtil.escape(Constants.CT_FHIR_JSON_NEW))); + assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); // Fetch the next page httpGet = new HttpGet(linkNext); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=" + UrlUtil.escape(Constants.CT_FHIR_JSON_NEW))); + assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); // Fetch the next page httpGet = new HttpGet(linkNext); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=" + UrlUtil.escape(Constants.CT_FHIR_JSON_NEW))); + assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchSearchServerDstu1Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchSearchServerDstu1Test.java index 7ac6de97773..840cb4d2de7 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchSearchServerDstu1Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchSearchServerDstu1Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.rest.server; -import static ca.uhn.fhir.util.UrlUtil.escape; +import static ca.uhn.fhir.util.UrlUtil.escapeUrlParam; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; @@ -110,10 +110,10 @@ public class SearchSearchServerDstu1Test { b.append("http://localhost:"); b.append(ourPort); b.append("/Patient?"); - b.append(escape("findPatientWithAndList")).append('=').append(escape("NE\\,NE,NE\\,NE")).append('&'); - b.append(escape("findPatientWithAndList")).append('=').append(escape("NE\\\\NE")).append('&'); - b.append(escape("findPatientWithAndList:exact")).append('=').append(escape("E\\$E")).append('&'); - b.append(escape("findPatientWithAndList:exact")).append('=').append(escape("E\\|E")).append('&'); + b.append(escapeUrlParam("findPatientWithAndList")).append('=').append(escapeUrlParam("NE\\,NE,NE\\,NE")).append('&'); + b.append(escapeUrlParam("findPatientWithAndList")).append('=').append(escapeUrlParam("NE\\\\NE")).append('&'); + b.append(escapeUrlParam("findPatientWithAndList:exact")).append('=').append(escapeUrlParam("E\\$E")).append('&'); + b.append(escapeUrlParam("findPatientWithAndList:exact")).append('=').append(escapeUrlParam("E\\|E")).append('&'); HttpGet httpGet = new HttpGet(b.toString()); @@ -416,7 +416,7 @@ public class SearchSearchServerDstu1Test { @Test public void testSearchWithTokenParameter() throws Exception { - String token = UrlUtil.escape("http://www.dmix.gov/vista/2957|301"); + String token = UrlUtil.escapeUrlParam("http://www.dmix.gov/vista/2957|301"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?tokenParam=" + token); HttpResponse status = ourClient.execute(httpGet); String responseContent = IOUtils.toString(status.getEntity().getContent()); diff --git a/hapi-fhir-utilities/src/main/java/org/hl7/fhir/utilities/xml/XMLUtil.java b/hapi-fhir-utilities/src/main/java/org/hl7/fhir/utilities/xml/XMLUtil.java index 5c8c2106963..4ceae56abd1 100644 --- a/hapi-fhir-utilities/src/main/java/org/hl7/fhir/utilities/xml/XMLUtil.java +++ b/hapi-fhir-utilities/src/main/java/org/hl7/fhir/utilities/xml/XMLUtil.java @@ -1,472 +1,472 @@ -/* -Copyright (c) 2011+, HL7, Inc -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ -package org.hl7.fhir.utilities.xml; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.List; -import java.util.Set; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.utilities.Utilities; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.ls.DOMImplementationLS; -import org.w3c.dom.ls.LSSerializer; -import org.xml.sax.SAXException; - -public class XMLUtil { - - public static final String SPACE_CHAR = "\u00A0"; - - public static boolean isNMToken(String name) { - if (name == null) - return false; - for (int i = 0; i < name.length(); i++) - if (!isNMTokenChar(name.charAt(i))) - return false; - return name.length() > 0; - } - - public static boolean isNMTokenChar(char c) { - return isLetter(c) || isDigit(c) || c == '.' || c == '-' || c == '_' || c == ':' || isCombiningChar(c) || isExtender(c); - } - - private static boolean isDigit(char c) { - return (c >= '\u0030' && c <= '\u0039') || (c >= '\u0660' && c <= '\u0669') || (c >= '\u06F0' && c <= '\u06F9') || - (c >= '\u0966' && c <= '\u096F') || (c >= '\u09E6' && c <= '\u09EF') || (c >= '\u0A66' && c <= '\u0A6F') || - (c >= '\u0AE6' && c <= '\u0AEF') || (c >= '\u0B66' && c <= '\u0B6F') || (c >= '\u0BE7' && c <= '\u0BEF') || - (c >= '\u0C66' && c <= '\u0C6F') || (c >= '\u0CE6' && c <= '\u0CEF') || (c >= '\u0D66' && c <= '\u0D6F') || - (c >= '\u0E50' && c <= '\u0E59') || (c >= '\u0ED0' && c <= '\u0ED9') || (c >= '\u0F20' && c <= '\u0F29'); - } - - private static boolean isCombiningChar(char c) { - return (c >= '\u0300' && c <= '\u0345') || (c >= '\u0360' && c <= '\u0361') || (c >= '\u0483' && c <= '\u0486') || - (c >= '\u0591' && c <= '\u05A1') || (c >= '\u05A3' && c <= '\u05B9') || (c >= '\u05BB' && c <= '\u05BD') || - c == '\u05BF' || (c >= '\u05C1' && c <= '\u05C2') || c == '\u05C4' || (c >= '\u064B' && c <= '\u0652') || - c == '\u0670' || (c >= '\u06D6' && c <= '\u06DC') || (c >= '\u06DD' && c <= '\u06DF') || (c >= '\u06E0' && c <= '\u06E4') || - (c >= '\u06E7' && c <= '\u06E8') || (c >= '\u06EA' && c <= '\u06ED') || (c >= '\u0901' && c <= '\u0903') || c == '\u093C' || - (c >= '\u093E' && c <= '\u094C') || c == '\u094D' || (c >= '\u0951' && c <= '\u0954') || (c >= '\u0962' && c <= '\u0963') || - (c >= '\u0981' && c <= '\u0983') || c == '\u09BC' || c == '\u09BE' || c == '\u09BF' || (c >= '\u09C0' && c <= '\u09C4') || - (c >= '\u09C7' && c <= '\u09C8') || (c >= '\u09CB' && c <= '\u09CD') || c == '\u09D7' || (c >= '\u09E2' && c <= '\u09E3') || - c == '\u0A02' || c == '\u0A3C' || c == '\u0A3E' || c == '\u0A3F' || (c >= '\u0A40' && c <= '\u0A42') || - (c >= '\u0A47' && c <= '\u0A48') || (c >= '\u0A4B' && c <= '\u0A4D') || (c >= '\u0A70' && c <= '\u0A71') || - (c >= '\u0A81' && c <= '\u0A83') || c == '\u0ABC' || (c >= '\u0ABE' && c <= '\u0AC5') || (c >= '\u0AC7' && c <= '\u0AC9') || - (c >= '\u0ACB' && c <= '\u0ACD') || (c >= '\u0B01' && c <= '\u0B03') || c == '\u0B3C' || (c >= '\u0B3E' && c <= '\u0B43') || - (c >= '\u0B47' && c <= '\u0B48') || (c >= '\u0B4B' && c <= '\u0B4D') || (c >= '\u0B56' && c <= '\u0B57') || - (c >= '\u0B82' && c <= '\u0B83') || (c >= '\u0BBE' && c <= '\u0BC2') || (c >= '\u0BC6' && c <= '\u0BC8') || - (c >= '\u0BCA' && c <= '\u0BCD') || c == '\u0BD7' || (c >= '\u0C01' && c <= '\u0C03') || (c >= '\u0C3E' && c <= '\u0C44') || - (c >= '\u0C46' && c <= '\u0C48') || (c >= '\u0C4A' && c <= '\u0C4D') || (c >= '\u0C55' && c <= '\u0C56') || - (c >= '\u0C82' && c <= '\u0C83') || (c >= '\u0CBE' && c <= '\u0CC4') || (c >= '\u0CC6' && c <= '\u0CC8') || - (c >= '\u0CCA' && c <= '\u0CCD') || (c >= '\u0CD5' && c <= '\u0CD6') || (c >= '\u0D02' && c <= '\u0D03') || - (c >= '\u0D3E' && c <= '\u0D43') || (c >= '\u0D46' && c <= '\u0D48') || (c >= '\u0D4A' && c <= '\u0D4D') || c == '\u0D57' || - c == '\u0E31' || (c >= '\u0E34' && c <= '\u0E3A') || (c >= '\u0E47' && c <= '\u0E4E') || c == '\u0EB1' || - (c >= '\u0EB4' && c <= '\u0EB9') || (c >= '\u0EBB' && c <= '\u0EBC') || (c >= '\u0EC8' && c <= '\u0ECD') || - (c >= '\u0F18' && c <= '\u0F19') || c == '\u0F35' || c == '\u0F37' || c == '\u0F39' || c == '\u0F3E' || c == '\u0F3F' || - (c >= '\u0F71' && c <= '\u0F84') || (c >= '\u0F86' && c <= '\u0F8B') || (c >= '\u0F90' && c <= '\u0F95') || c == '\u0F97' || - (c >= '\u0F99' && c <= '\u0FAD') || (c >= '\u0FB1' && c <= '\u0FB7') || c == '\u0FB9' || (c >= '\u20D0' && c <= '\u20DC') || - c == '\u20E1' || (c >= '\u302A' && c <= '\u302F') || c == '\u3099' || c == '\u309A'; - } - - private static boolean isExtender(char c) { - return c == '\u00B7' || c == '\u02D0' || c == '\u02D1' || c == '\u0387' || c == '\u0640' || c == '\u0E46' || - c == '\u0EC6' || c == '\u3005' || (c >= '\u3031' && c <= '\u3035') || (c >= '\u309D' && c <= '\u309E') || - (c >= '\u30FC' && c <= '\u30FE'); - } - - private static boolean isLetter(char c) { - return isBaseChar(c) || isIdeographic(c); - } - - private static boolean isBaseChar(char c) { - return (c >= '\u0041' && c <= '\u005A') || (c >= '\u0061' && c <= '\u007A') || (c >= '\u00C0' && c <= '\u00D6') || - (c >= '\u00D8' && c <= '\u00F6') || (c >= '\u00F8' && c <= '\u00FF') || (c >= '\u0100' && c <= '\u0131') || - (c >= '\u0134' && c <= '\u013E') || (c >= '\u0141' && c <= '\u0148') || (c >= '\u014A' && c <= '\u017E') || - (c >= '\u0180' && c <= '\u01C3') || (c >= '\u01CD' && c <= '\u01F0') || (c >= '\u01F4' && c <= '\u01F5') || - (c >= '\u01FA' && c <= '\u0217') || (c >= '\u0250' && c <= '\u02A8') || (c >= '\u02BB' && c <= '\u02C1') || - c == '\u0386' || (c >= '\u0388' && c <= '\u038A') || c == '\u038C' || (c >= '\u038E' && c <= '\u03A1') || - (c >= '\u03A3' && c <= '\u03CE') || (c >= '\u03D0' && c <= '\u03D6') || c == '\u03DA' || c == '\u03DC' || c == '\u03DE' || - c == '\u03E0' || (c >= '\u03E2' && c <= '\u03F3') || (c >= '\u0401' && c <= '\u040C') || (c >= '\u040E' && c <= '\u044F') || - (c >= '\u0451' && c <= '\u045C') || (c >= '\u045E' && c <= '\u0481') || (c >= '\u0490' && c <= '\u04C4') || - (c >= '\u04C7' && c <= '\u04C8') || (c >= '\u04CB' && c <= '\u04CC') || (c >= '\u04D0' && c <= '\u04EB') || - (c >= '\u04EE' && c <= '\u04F5') || (c >= '\u04F8' && c <= '\u04F9') || (c >= '\u0531' && c <= '\u0556') || - c == '\u0559' || (c >= '\u0561' && c <= '\u0586') || (c >= '\u05D0' && c <= '\u05EA') || (c >= '\u05F0' && c <= '\u05F2') || - (c >= '\u0621' && c <= '\u063A') || (c >= '\u0641' && c <= '\u064A') || (c >= '\u0671' && c <= '\u06B7') || - (c >= '\u06BA' && c <= '\u06BE') || (c >= '\u06C0' && c <= '\u06CE') || (c >= '\u06D0' && c <= '\u06D3') || - c == '\u06D5' || (c >= '\u06E5' && c <= '\u06E6') || (c >= '\u0905' && c <= '\u0939') || c == '\u093D' || - (c >= '\u0958' && c <= '\u0961') || (c >= '\u0985' && c <= '\u098C') || (c >= '\u098F' && c <= '\u0990') || - (c >= '\u0993' && c <= '\u09A8') || (c >= '\u09AA' && c <= '\u09B0') || c == '\u09B2' || - (c >= '\u09B6' && c <= '\u09B9') || (c >= '\u09DC' && c <= '\u09DD') || (c >= '\u09DF' && c <= '\u09E1') || - (c >= '\u09F0' && c <= '\u09F1') || (c >= '\u0A05' && c <= '\u0A0A') || (c >= '\u0A0F' && c <= '\u0A10') || - (c >= '\u0A13' && c <= '\u0A28') || (c >= '\u0A2A' && c <= '\u0A30') || (c >= '\u0A32' && c <= '\u0A33') || - (c >= '\u0A35' && c <= '\u0A36') || (c >= '\u0A38' && c <= '\u0A39') || (c >= '\u0A59' && c <= '\u0A5C') || - c == '\u0A5E' || (c >= '\u0A72' && c <= '\u0A74') || (c >= '\u0A85' && c <= '\u0A8B') || c == '\u0A8D' || - (c >= '\u0A8F' && c <= '\u0A91') || (c >= '\u0A93' && c <= '\u0AA8') || (c >= '\u0AAA' && c <= '\u0AB0') || - (c >= '\u0AB2' && c <= '\u0AB3') || (c >= '\u0AB5' && c <= '\u0AB9') || c == '\u0ABD' || c == '\u0AE0' || - (c >= '\u0B05' && c <= '\u0B0C') || (c >= '\u0B0F' && c <= '\u0B10') || (c >= '\u0B13' && c <= '\u0B28') || - (c >= '\u0B2A' && c <= '\u0B30') || (c >= '\u0B32' && c <= '\u0B33') || (c >= '\u0B36' && c <= '\u0B39') || - c == '\u0B3D' || (c >= '\u0B5C' && c <= '\u0B5D') || (c >= '\u0B5F' && c <= '\u0B61') || - (c >= '\u0B85' && c <= '\u0B8A') || (c >= '\u0B8E' && c <= '\u0B90') || (c >= '\u0B92' && c <= '\u0B95') || - (c >= '\u0B99' && c <= '\u0B9A') || c == '\u0B9C' || (c >= '\u0B9E' && c <= '\u0B9F') || - (c >= '\u0BA3' && c <= '\u0BA4') || (c >= '\u0BA8' && c <= '\u0BAA') || (c >= '\u0BAE' && c <= '\u0BB5') || - (c >= '\u0BB7' && c <= '\u0BB9') || (c >= '\u0C05' && c <= '\u0C0C') || (c >= '\u0C0E' && c <= '\u0C10') || - (c >= '\u0C12' && c <= '\u0C28') || (c >= '\u0C2A' && c <= '\u0C33') || (c >= '\u0C35' && c <= '\u0C39') || - (c >= '\u0C60' && c <= '\u0C61') || (c >= '\u0C85' && c <= '\u0C8C') || (c >= '\u0C8E' && c <= '\u0C90') || - (c >= '\u0C92' && c <= '\u0CA8') || (c >= '\u0CAA' && c <= '\u0CB3') || (c >= '\u0CB5' && c <= '\u0CB9') || - c == '\u0CDE' || (c >= '\u0CE0' && c <= '\u0CE1') || (c >= '\u0D05' && c <= '\u0D0C') || - (c >= '\u0D0E' && c <= '\u0D10') || (c >= '\u0D12' && c <= '\u0D28') || (c >= '\u0D2A' && c <= '\u0D39') || - (c >= '\u0D60' && c <= '\u0D61') || (c >= '\u0E01' && c <= '\u0E2E') || c == '\u0E30' || - (c >= '\u0E32' && c <= '\u0E33') || (c >= '\u0E40' && c <= '\u0E45') || (c >= '\u0E81' && c <= '\u0E82') || - c == '\u0E84' || (c >= '\u0E87' && c <= '\u0E88') || c == '\u0E8A' || c == '\u0E8D' || (c >= '\u0E94' && c <= '\u0E97') || - (c >= '\u0E99' && c <= '\u0E9F') || (c >= '\u0EA1' && c <= '\u0EA3') || c == '\u0EA5' || c == '\u0EA7' || - (c >= '\u0EAA' && c <= '\u0EAB') || (c >= '\u0EAD' && c <= '\u0EAE') || c == '\u0EB0' || - (c >= '\u0EB2' && c <= '\u0EB3') || c == '\u0EBD' || (c >= '\u0EC0' && c <= '\u0EC4') || - (c >= '\u0F40' && c <= '\u0F47') || (c >= '\u0F49' && c <= '\u0F69') || (c >= '\u10A0' && c <= '\u10C5') || - (c >= '\u10D0' && c <= '\u10F6') || c == '\u1100' || (c >= '\u1102' && c <= '\u1103') || - (c >= '\u1105' && c <= '\u1107') || c == '\u1109' || (c >= '\u110B' && c <= '\u110C') || - (c >= '\u110E' && c <= '\u1112') || c == '\u113C' || c == '\u113E' || c == '\u1140' || c == '\u114C' || - c == '\u114E' || c == '\u1150' || (c >= '\u1154' && c <= '\u1155') || c == '\u1159' || - (c >= '\u115F' && c <= '\u1161') || c == '\u1163' || c == '\u1165' || c == '\u1167' || c == '\u1169' || - (c >= '\u116D' && c <= '\u116E') || (c >= '\u1172' && c <= '\u1173') || c == '\u1175' || - c == '\u119E' || c == '\u11A8' || c == '\u11AB' || (c >= '\u11AE' && c <= '\u11AF') || - (c >= '\u11B7' && c <= '\u11B8') || c == '\u11BA' || (c >= '\u11BC' && c <= '\u11C2') || - c == '\u11EB' || c == '\u11F0' || c == '\u11F9' || (c >= '\u1E00' && c <= '\u1E9B') || (c >= '\u1EA0' && c <= '\u1EF9') || - (c >= '\u1F00' && c <= '\u1F15') || (c >= '\u1F18' && c <= '\u1F1D') || (c >= '\u1F20' && c <= '\u1F45') || - (c >= '\u1F48' && c <= '\u1F4D') || (c >= '\u1F50' && c <= '\u1F57') || c == '\u1F59' || c == '\u1F5B' || c == '\u1F5D' || - (c >= '\u1F5F' && c <= '\u1F7D') || (c >= '\u1F80' && c <= '\u1FB4') || (c >= '\u1FB6' && c <= '\u1FBC') || - c == '\u1FBE' || (c >= '\u1FC2' && c <= '\u1FC4') || (c >= '\u1FC6' && c <= '\u1FCC') || - (c >= '\u1FD0' && c <= '\u1FD3') || (c >= '\u1FD6' && c <= '\u1FDB') || (c >= '\u1FE0' && c <= '\u1FEC') || - (c >= '\u1FF2' && c <= '\u1FF4') || (c >= '\u1FF6' && c <= '\u1FFC') || c == '\u2126' || - (c >= '\u212A' && c <= '\u212B') || c == '\u212E' || (c >= '\u2180' && c <= '\u2182') || - (c >= '\u3041' && c <= '\u3094') || (c >= '\u30A1' && c <= '\u30FA') || (c >= '\u3105' && c <= '\u312C') || - (c >= '\uAC00' && c <= '\uD7A3'); - } - - private static boolean isIdeographic(char c) { - return (c >= '\u4E00' && c <= '\u9FA5') || c == '\u3007' || (c >= '\u3021' && c <= '\u3029'); - } - - public static String determineEncoding(InputStream stream) throws IOException { - stream.mark(20000); - try { - int b0 = stream.read(); - int b1 = stream.read(); - int b2 = stream.read(); - int b3 = stream.read(); - - if (b0 == 0xFE && b1 == 0xFF) - return "UTF-16BE"; - else if (b0 == 0xFF && b1 == 0xFE) - return "UTF-16LE"; - else if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF ) - return "UTF-8"; - else if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) - return "UTF-16BE"; - else if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) - return "UTF-16LE"; - else if (b0 == 0x3C && b1 == 0x3F && b2 == 0x78 && b3 == 0x6D) { -// UTF-8, ISO 646, ASCII, some part of ISO 8859, Shift-JIS, EUC, or any other 7-bit, 8-bit, or mixed-width encoding -// which ensures that the characters of ASCII have their normal positions, width, and values; the actual encoding -// declaration must be read to detect which of these applies, but since all of these encodings use the same bit patterns -// for the relevant ASCII characters, the encoding declaration itself may be read reliably - InputStreamReader rdr = new InputStreamReader(stream, "US-ASCII"); - String hdr = readFirstLine(rdr); - return extractEncoding(hdr); - } else - return null; - } finally { - stream.reset(); - } - } - - private static String extractEncoding(String hdr) { - int i = hdr.indexOf("encoding="); - if (i == -1) - return null; - hdr = hdr.substring(i+9); - char sep = hdr.charAt(0); - hdr = hdr.substring(1); - i = hdr.indexOf(sep); - if (i == -1) - return null; - return hdr.substring(0, i); - } - - private static String readFirstLine(InputStreamReader rdr) throws IOException { - char[] buf = new char[1]; - StringBuffer bldr = new StringBuffer(); - rdr.read(buf); - while (buf[0] != '>') { - bldr.append(buf[0]); - rdr.read(buf); - } - return bldr.toString(); - } - - - public static boolean charSetImpliesAscii(String charset) { - return charset.equals("ISO-8859-1") || charset.equals("US-ASCII"); - } - - - /** - * Converts the raw characters to XML escape characters. - * - * @param rawContent - * @param charset Null when charset is not known, so we assume it's unicode - * @param isNoLines - * @return escape string - */ - public static String escapeXML(String rawContent, String charset, boolean isNoLines) { - if (rawContent == null) - return ""; - else { - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < rawContent.length(); i++) { - char ch = rawContent.charAt(i); - if (ch == '\'') - sb.append("'"); - else if (ch == '&') - sb.append("&"); - else if (ch == '"') - sb.append("""); - else if (ch == '<') - sb.append("<"); - else if (ch == '>') - sb.append(">"); - else if (ch > '~' && charset != null && charSetImpliesAscii(charset)) - // TODO - why is hashcode the only way to get the unicode number for the character - // in jre 5.0? - sb.append("&#x"+Integer.toHexString(ch).toUpperCase()+";"); - else if (isNoLines) { - if (ch == '\r') - sb.append(" "); - else if (ch != '\n') - sb.append(ch); - } - else - sb.append(ch); - } - return sb.toString(); - } - } - - public static Element getFirstChild(Element e) { - if (e == null) - return null; - Node n = e.getFirstChild(); - while (n != null && n.getNodeType() != Node.ELEMENT_NODE) - n = n.getNextSibling(); - return (Element) n; - } - - public static Element getNamedChild(Element e, String name) { - Element c = getFirstChild(e); - while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName())) - c = getNextSibling(c); - return c; - } - - public static Element getNextSibling(Element e) { - Node n = e.getNextSibling(); - while (n != null && n.getNodeType() != Node.ELEMENT_NODE) - n = n.getNextSibling(); - return (Element) n; - } - - public static void getNamedChildren(Element e, String name, List set) { - Element c = getFirstChild(e); - while (c != null) { - if (name.equals(c.getLocalName()) || name.equals(c.getNodeName()) ) - set.add(c); - c = getNextSibling(c); - } - } - - public static String htmlToXmlEscapedPlainText(Element r) { - StringBuilder s = new StringBuilder(); - Node n = r.getFirstChild(); - boolean ws = false; - while (n != null) { - if (n.getNodeType() == Node.TEXT_NODE) { - String t = n.getTextContent().trim(); - if (Utilities.noString(t)) - ws = true; - else { - if (ws) - s.append(" "); - ws = false; - s.append(t); - } - } - if (n.getNodeType() == Node.ELEMENT_NODE) { - if (ws) - s.append(" "); - ws = false; - s.append(htmlToXmlEscapedPlainText((Element) n)); - if (r.getNodeName().equals("br") || r.getNodeName().equals("p")) - s.append("\r\n"); - } - n = n.getNextSibling(); - } - return s.toString(); - } - - public static String htmlToXmlEscapedPlainText(String definition) throws ParserConfigurationException, SAXException, IOException { - return htmlToXmlEscapedPlainText(parseToDom("
    "+definition+"
    ").getDocumentElement()); - } - - public static String elementToString(Element el) { - if (el == null) - return ""; - Document document = el.getOwnerDocument(); - DOMImplementationLS domImplLS = (DOMImplementationLS) document - .getImplementation(); - LSSerializer serializer = domImplLS.createLSSerializer(); - return serializer.writeToString(el); - } - - public static String getNamedChildValue(Element element, String name) { - Element e = getNamedChild(element, name); - return e == null ? null : e.getAttribute("value"); - } - - public static void setNamedChildValue(Element element, String name, String value) throws FHIRException { - Element e = getNamedChild(element, name); - if (e == null) - throw new FHIRException("unable to find element "+name); - e.setAttribute("value", value); - } - - - public static void getNamedChildrenWithWildcard(Element focus, String name, List children) { - Element c = getFirstChild(focus); - while (c != null) { - String n = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); - if (name.equals(n) || (name.endsWith("[x]") && n.startsWith(name.substring(0, name.length()-3)))) - children.add(c); - c = getNextSibling(c); - } - } - - public static void getNamedChildrenWithTails(Element focus, String name, List children, Set typeTails) { - Element c = getFirstChild(focus); - while (c != null) { - String n = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); - if (n.equals(name) || (!n.equals("responseCode") && (n.startsWith(name) && typeTails.contains(n.substring(name.length()))))) - children.add(c); - c = getNextSibling(c); - } - } - - public static boolean hasNamedChild(Element e, String name) { - Element c = getFirstChild(e); - while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName())) - c = getNextSibling(c); - return c != null; - } - - public static Document parseToDom(String content) throws ParserConfigurationException, SAXException, IOException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(false); - DocumentBuilder builder = factory.newDocumentBuilder(); - return builder.parse(new ByteArrayInputStream(content.getBytes())); - } - - public static Document parseFileToDom(String filename) throws ParserConfigurationException, SAXException, IOException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(false); - DocumentBuilder builder = factory.newDocumentBuilder(); - return builder.parse(new FileInputStream(filename)); - } - - public static Element getLastChild(Element e) { - if (e == null) - return null; - Node n = e.getLastChild(); - while (n != null && n.getNodeType() != Node.ELEMENT_NODE) - n = n.getPreviousSibling(); - return (Element) n; - } - - public static Element getPrevSibling(Element e) { - Node n = e.getPreviousSibling(); - while (n != null && n.getNodeType() != Node.ELEMENT_NODE) - n = n.getPreviousSibling(); - return (Element) n; - } - - public static String getNamedChildAttribute(Element element, String name, String aname) { - Element e = getNamedChild(element, name); - return e == null ? null : e.getAttribute(aname); - } - - public static void writeDomToFile(Document doc, String filename) throws TransformerException { - TransformerFactory transformerFactory = TransformerFactory.newInstance(); - Transformer transformer = transformerFactory.newTransformer(); - DOMSource source = new DOMSource(doc); - StreamResult streamResult = new StreamResult(new File(filename)); - transformer.transform(source, streamResult); - } - - public static String getXsiType(org.w3c.dom.Element element) { - Attr a = element.getAttributeNodeNS("http://www.w3.org/2001/XMLSchema-instance", "type"); - return (a == null ? null : a.getTextContent()); - - } - - public static String getDirectText(org.w3c.dom.Element node) { - Node n = node.getFirstChild(); - StringBuilder b = new StringBuilder(); - while (n != null) { - if (n.getNodeType() == Node.TEXT_NODE) - b.append(n.getTextContent()); - n = n.getNextSibling(); - } - return b.toString().trim(); - } - - -} +/* +Copyright (c) 2011+, HL7, Inc +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +package org.hl7.fhir.utilities.xml; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.utilities.Utilities; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSSerializer; +import org.xml.sax.SAXException; + +public class XMLUtil { + + public static final String SPACE_CHAR = "\u00A0"; + + public static boolean isNMToken(String name) { + if (name == null) + return false; + for (int i = 0; i < name.length(); i++) + if (!isNMTokenChar(name.charAt(i))) + return false; + return name.length() > 0; + } + + public static boolean isNMTokenChar(char c) { + return isLetter(c) || isDigit(c) || c == '.' || c == '-' || c == '_' || c == ':' || isCombiningChar(c) || isExtender(c); + } + + private static boolean isDigit(char c) { + return (c >= '\u0030' && c <= '\u0039') || (c >= '\u0660' && c <= '\u0669') || (c >= '\u06F0' && c <= '\u06F9') || + (c >= '\u0966' && c <= '\u096F') || (c >= '\u09E6' && c <= '\u09EF') || (c >= '\u0A66' && c <= '\u0A6F') || + (c >= '\u0AE6' && c <= '\u0AEF') || (c >= '\u0B66' && c <= '\u0B6F') || (c >= '\u0BE7' && c <= '\u0BEF') || + (c >= '\u0C66' && c <= '\u0C6F') || (c >= '\u0CE6' && c <= '\u0CEF') || (c >= '\u0D66' && c <= '\u0D6F') || + (c >= '\u0E50' && c <= '\u0E59') || (c >= '\u0ED0' && c <= '\u0ED9') || (c >= '\u0F20' && c <= '\u0F29'); + } + + private static boolean isCombiningChar(char c) { + return (c >= '\u0300' && c <= '\u0345') || (c >= '\u0360' && c <= '\u0361') || (c >= '\u0483' && c <= '\u0486') || + (c >= '\u0591' && c <= '\u05A1') || (c >= '\u05A3' && c <= '\u05B9') || (c >= '\u05BB' && c <= '\u05BD') || + c == '\u05BF' || (c >= '\u05C1' && c <= '\u05C2') || c == '\u05C4' || (c >= '\u064B' && c <= '\u0652') || + c == '\u0670' || (c >= '\u06D6' && c <= '\u06DC') || (c >= '\u06DD' && c <= '\u06DF') || (c >= '\u06E0' && c <= '\u06E4') || + (c >= '\u06E7' && c <= '\u06E8') || (c >= '\u06EA' && c <= '\u06ED') || (c >= '\u0901' && c <= '\u0903') || c == '\u093C' || + (c >= '\u093E' && c <= '\u094C') || c == '\u094D' || (c >= '\u0951' && c <= '\u0954') || (c >= '\u0962' && c <= '\u0963') || + (c >= '\u0981' && c <= '\u0983') || c == '\u09BC' || c == '\u09BE' || c == '\u09BF' || (c >= '\u09C0' && c <= '\u09C4') || + (c >= '\u09C7' && c <= '\u09C8') || (c >= '\u09CB' && c <= '\u09CD') || c == '\u09D7' || (c >= '\u09E2' && c <= '\u09E3') || + c == '\u0A02' || c == '\u0A3C' || c == '\u0A3E' || c == '\u0A3F' || (c >= '\u0A40' && c <= '\u0A42') || + (c >= '\u0A47' && c <= '\u0A48') || (c >= '\u0A4B' && c <= '\u0A4D') || (c >= '\u0A70' && c <= '\u0A71') || + (c >= '\u0A81' && c <= '\u0A83') || c == '\u0ABC' || (c >= '\u0ABE' && c <= '\u0AC5') || (c >= '\u0AC7' && c <= '\u0AC9') || + (c >= '\u0ACB' && c <= '\u0ACD') || (c >= '\u0B01' && c <= '\u0B03') || c == '\u0B3C' || (c >= '\u0B3E' && c <= '\u0B43') || + (c >= '\u0B47' && c <= '\u0B48') || (c >= '\u0B4B' && c <= '\u0B4D') || (c >= '\u0B56' && c <= '\u0B57') || + (c >= '\u0B82' && c <= '\u0B83') || (c >= '\u0BBE' && c <= '\u0BC2') || (c >= '\u0BC6' && c <= '\u0BC8') || + (c >= '\u0BCA' && c <= '\u0BCD') || c == '\u0BD7' || (c >= '\u0C01' && c <= '\u0C03') || (c >= '\u0C3E' && c <= '\u0C44') || + (c >= '\u0C46' && c <= '\u0C48') || (c >= '\u0C4A' && c <= '\u0C4D') || (c >= '\u0C55' && c <= '\u0C56') || + (c >= '\u0C82' && c <= '\u0C83') || (c >= '\u0CBE' && c <= '\u0CC4') || (c >= '\u0CC6' && c <= '\u0CC8') || + (c >= '\u0CCA' && c <= '\u0CCD') || (c >= '\u0CD5' && c <= '\u0CD6') || (c >= '\u0D02' && c <= '\u0D03') || + (c >= '\u0D3E' && c <= '\u0D43') || (c >= '\u0D46' && c <= '\u0D48') || (c >= '\u0D4A' && c <= '\u0D4D') || c == '\u0D57' || + c == '\u0E31' || (c >= '\u0E34' && c <= '\u0E3A') || (c >= '\u0E47' && c <= '\u0E4E') || c == '\u0EB1' || + (c >= '\u0EB4' && c <= '\u0EB9') || (c >= '\u0EBB' && c <= '\u0EBC') || (c >= '\u0EC8' && c <= '\u0ECD') || + (c >= '\u0F18' && c <= '\u0F19') || c == '\u0F35' || c == '\u0F37' || c == '\u0F39' || c == '\u0F3E' || c == '\u0F3F' || + (c >= '\u0F71' && c <= '\u0F84') || (c >= '\u0F86' && c <= '\u0F8B') || (c >= '\u0F90' && c <= '\u0F95') || c == '\u0F97' || + (c >= '\u0F99' && c <= '\u0FAD') || (c >= '\u0FB1' && c <= '\u0FB7') || c == '\u0FB9' || (c >= '\u20D0' && c <= '\u20DC') || + c == '\u20E1' || (c >= '\u302A' && c <= '\u302F') || c == '\u3099' || c == '\u309A'; + } + + private static boolean isExtender(char c) { + return c == '\u00B7' || c == '\u02D0' || c == '\u02D1' || c == '\u0387' || c == '\u0640' || c == '\u0E46' || + c == '\u0EC6' || c == '\u3005' || (c >= '\u3031' && c <= '\u3035') || (c >= '\u309D' && c <= '\u309E') || + (c >= '\u30FC' && c <= '\u30FE'); + } + + private static boolean isLetter(char c) { + return isBaseChar(c) || isIdeographic(c); + } + + private static boolean isBaseChar(char c) { + return (c >= '\u0041' && c <= '\u005A') || (c >= '\u0061' && c <= '\u007A') || (c >= '\u00C0' && c <= '\u00D6') || + (c >= '\u00D8' && c <= '\u00F6') || (c >= '\u00F8' && c <= '\u00FF') || (c >= '\u0100' && c <= '\u0131') || + (c >= '\u0134' && c <= '\u013E') || (c >= '\u0141' && c <= '\u0148') || (c >= '\u014A' && c <= '\u017E') || + (c >= '\u0180' && c <= '\u01C3') || (c >= '\u01CD' && c <= '\u01F0') || (c >= '\u01F4' && c <= '\u01F5') || + (c >= '\u01FA' && c <= '\u0217') || (c >= '\u0250' && c <= '\u02A8') || (c >= '\u02BB' && c <= '\u02C1') || + c == '\u0386' || (c >= '\u0388' && c <= '\u038A') || c == '\u038C' || (c >= '\u038E' && c <= '\u03A1') || + (c >= '\u03A3' && c <= '\u03CE') || (c >= '\u03D0' && c <= '\u03D6') || c == '\u03DA' || c == '\u03DC' || c == '\u03DE' || + c == '\u03E0' || (c >= '\u03E2' && c <= '\u03F3') || (c >= '\u0401' && c <= '\u040C') || (c >= '\u040E' && c <= '\u044F') || + (c >= '\u0451' && c <= '\u045C') || (c >= '\u045E' && c <= '\u0481') || (c >= '\u0490' && c <= '\u04C4') || + (c >= '\u04C7' && c <= '\u04C8') || (c >= '\u04CB' && c <= '\u04CC') || (c >= '\u04D0' && c <= '\u04EB') || + (c >= '\u04EE' && c <= '\u04F5') || (c >= '\u04F8' && c <= '\u04F9') || (c >= '\u0531' && c <= '\u0556') || + c == '\u0559' || (c >= '\u0561' && c <= '\u0586') || (c >= '\u05D0' && c <= '\u05EA') || (c >= '\u05F0' && c <= '\u05F2') || + (c >= '\u0621' && c <= '\u063A') || (c >= '\u0641' && c <= '\u064A') || (c >= '\u0671' && c <= '\u06B7') || + (c >= '\u06BA' && c <= '\u06BE') || (c >= '\u06C0' && c <= '\u06CE') || (c >= '\u06D0' && c <= '\u06D3') || + c == '\u06D5' || (c >= '\u06E5' && c <= '\u06E6') || (c >= '\u0905' && c <= '\u0939') || c == '\u093D' || + (c >= '\u0958' && c <= '\u0961') || (c >= '\u0985' && c <= '\u098C') || (c >= '\u098F' && c <= '\u0990') || + (c >= '\u0993' && c <= '\u09A8') || (c >= '\u09AA' && c <= '\u09B0') || c == '\u09B2' || + (c >= '\u09B6' && c <= '\u09B9') || (c >= '\u09DC' && c <= '\u09DD') || (c >= '\u09DF' && c <= '\u09E1') || + (c >= '\u09F0' && c <= '\u09F1') || (c >= '\u0A05' && c <= '\u0A0A') || (c >= '\u0A0F' && c <= '\u0A10') || + (c >= '\u0A13' && c <= '\u0A28') || (c >= '\u0A2A' && c <= '\u0A30') || (c >= '\u0A32' && c <= '\u0A33') || + (c >= '\u0A35' && c <= '\u0A36') || (c >= '\u0A38' && c <= '\u0A39') || (c >= '\u0A59' && c <= '\u0A5C') || + c == '\u0A5E' || (c >= '\u0A72' && c <= '\u0A74') || (c >= '\u0A85' && c <= '\u0A8B') || c == '\u0A8D' || + (c >= '\u0A8F' && c <= '\u0A91') || (c >= '\u0A93' && c <= '\u0AA8') || (c >= '\u0AAA' && c <= '\u0AB0') || + (c >= '\u0AB2' && c <= '\u0AB3') || (c >= '\u0AB5' && c <= '\u0AB9') || c == '\u0ABD' || c == '\u0AE0' || + (c >= '\u0B05' && c <= '\u0B0C') || (c >= '\u0B0F' && c <= '\u0B10') || (c >= '\u0B13' && c <= '\u0B28') || + (c >= '\u0B2A' && c <= '\u0B30') || (c >= '\u0B32' && c <= '\u0B33') || (c >= '\u0B36' && c <= '\u0B39') || + c == '\u0B3D' || (c >= '\u0B5C' && c <= '\u0B5D') || (c >= '\u0B5F' && c <= '\u0B61') || + (c >= '\u0B85' && c <= '\u0B8A') || (c >= '\u0B8E' && c <= '\u0B90') || (c >= '\u0B92' && c <= '\u0B95') || + (c >= '\u0B99' && c <= '\u0B9A') || c == '\u0B9C' || (c >= '\u0B9E' && c <= '\u0B9F') || + (c >= '\u0BA3' && c <= '\u0BA4') || (c >= '\u0BA8' && c <= '\u0BAA') || (c >= '\u0BAE' && c <= '\u0BB5') || + (c >= '\u0BB7' && c <= '\u0BB9') || (c >= '\u0C05' && c <= '\u0C0C') || (c >= '\u0C0E' && c <= '\u0C10') || + (c >= '\u0C12' && c <= '\u0C28') || (c >= '\u0C2A' && c <= '\u0C33') || (c >= '\u0C35' && c <= '\u0C39') || + (c >= '\u0C60' && c <= '\u0C61') || (c >= '\u0C85' && c <= '\u0C8C') || (c >= '\u0C8E' && c <= '\u0C90') || + (c >= '\u0C92' && c <= '\u0CA8') || (c >= '\u0CAA' && c <= '\u0CB3') || (c >= '\u0CB5' && c <= '\u0CB9') || + c == '\u0CDE' || (c >= '\u0CE0' && c <= '\u0CE1') || (c >= '\u0D05' && c <= '\u0D0C') || + (c >= '\u0D0E' && c <= '\u0D10') || (c >= '\u0D12' && c <= '\u0D28') || (c >= '\u0D2A' && c <= '\u0D39') || + (c >= '\u0D60' && c <= '\u0D61') || (c >= '\u0E01' && c <= '\u0E2E') || c == '\u0E30' || + (c >= '\u0E32' && c <= '\u0E33') || (c >= '\u0E40' && c <= '\u0E45') || (c >= '\u0E81' && c <= '\u0E82') || + c == '\u0E84' || (c >= '\u0E87' && c <= '\u0E88') || c == '\u0E8A' || c == '\u0E8D' || (c >= '\u0E94' && c <= '\u0E97') || + (c >= '\u0E99' && c <= '\u0E9F') || (c >= '\u0EA1' && c <= '\u0EA3') || c == '\u0EA5' || c == '\u0EA7' || + (c >= '\u0EAA' && c <= '\u0EAB') || (c >= '\u0EAD' && c <= '\u0EAE') || c == '\u0EB0' || + (c >= '\u0EB2' && c <= '\u0EB3') || c == '\u0EBD' || (c >= '\u0EC0' && c <= '\u0EC4') || + (c >= '\u0F40' && c <= '\u0F47') || (c >= '\u0F49' && c <= '\u0F69') || (c >= '\u10A0' && c <= '\u10C5') || + (c >= '\u10D0' && c <= '\u10F6') || c == '\u1100' || (c >= '\u1102' && c <= '\u1103') || + (c >= '\u1105' && c <= '\u1107') || c == '\u1109' || (c >= '\u110B' && c <= '\u110C') || + (c >= '\u110E' && c <= '\u1112') || c == '\u113C' || c == '\u113E' || c == '\u1140' || c == '\u114C' || + c == '\u114E' || c == '\u1150' || (c >= '\u1154' && c <= '\u1155') || c == '\u1159' || + (c >= '\u115F' && c <= '\u1161') || c == '\u1163' || c == '\u1165' || c == '\u1167' || c == '\u1169' || + (c >= '\u116D' && c <= '\u116E') || (c >= '\u1172' && c <= '\u1173') || c == '\u1175' || + c == '\u119E' || c == '\u11A8' || c == '\u11AB' || (c >= '\u11AE' && c <= '\u11AF') || + (c >= '\u11B7' && c <= '\u11B8') || c == '\u11BA' || (c >= '\u11BC' && c <= '\u11C2') || + c == '\u11EB' || c == '\u11F0' || c == '\u11F9' || (c >= '\u1E00' && c <= '\u1E9B') || (c >= '\u1EA0' && c <= '\u1EF9') || + (c >= '\u1F00' && c <= '\u1F15') || (c >= '\u1F18' && c <= '\u1F1D') || (c >= '\u1F20' && c <= '\u1F45') || + (c >= '\u1F48' && c <= '\u1F4D') || (c >= '\u1F50' && c <= '\u1F57') || c == '\u1F59' || c == '\u1F5B' || c == '\u1F5D' || + (c >= '\u1F5F' && c <= '\u1F7D') || (c >= '\u1F80' && c <= '\u1FB4') || (c >= '\u1FB6' && c <= '\u1FBC') || + c == '\u1FBE' || (c >= '\u1FC2' && c <= '\u1FC4') || (c >= '\u1FC6' && c <= '\u1FCC') || + (c >= '\u1FD0' && c <= '\u1FD3') || (c >= '\u1FD6' && c <= '\u1FDB') || (c >= '\u1FE0' && c <= '\u1FEC') || + (c >= '\u1FF2' && c <= '\u1FF4') || (c >= '\u1FF6' && c <= '\u1FFC') || c == '\u2126' || + (c >= '\u212A' && c <= '\u212B') || c == '\u212E' || (c >= '\u2180' && c <= '\u2182') || + (c >= '\u3041' && c <= '\u3094') || (c >= '\u30A1' && c <= '\u30FA') || (c >= '\u3105' && c <= '\u312C') || + (c >= '\uAC00' && c <= '\uD7A3'); + } + + private static boolean isIdeographic(char c) { + return (c >= '\u4E00' && c <= '\u9FA5') || c == '\u3007' || (c >= '\u3021' && c <= '\u3029'); + } + + public static String determineEncoding(InputStream stream) throws IOException { + stream.mark(20000); + try { + int b0 = stream.read(); + int b1 = stream.read(); + int b2 = stream.read(); + int b3 = stream.read(); + + if (b0 == 0xFE && b1 == 0xFF) + return "UTF-16BE"; + else if (b0 == 0xFF && b1 == 0xFE) + return "UTF-16LE"; + else if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF ) + return "UTF-8"; + else if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) + return "UTF-16BE"; + else if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) + return "UTF-16LE"; + else if (b0 == 0x3C && b1 == 0x3F && b2 == 0x78 && b3 == 0x6D) { +// UTF-8, ISO 646, ASCII, some part of ISO 8859, Shift-JIS, EUC, or any other 7-bit, 8-bit, or mixed-width encoding +// which ensures that the characters of ASCII have their normal positions, width, and values; the actual encoding +// declaration must be read to detect which of these applies, but since all of these encodings use the same bit patterns +// for the relevant ASCII characters, the encoding declaration itself may be read reliably + InputStreamReader rdr = new InputStreamReader(stream, "US-ASCII"); + String hdr = readFirstLine(rdr); + return extractEncoding(hdr); + } else + return null; + } finally { + stream.reset(); + } + } + + private static String extractEncoding(String hdr) { + int i = hdr.indexOf("encoding="); + if (i == -1) + return null; + hdr = hdr.substring(i+9); + char sep = hdr.charAt(0); + hdr = hdr.substring(1); + i = hdr.indexOf(sep); + if (i == -1) + return null; + return hdr.substring(0, i); + } + + private static String readFirstLine(InputStreamReader rdr) throws IOException { + char[] buf = new char[1]; + StringBuffer bldr = new StringBuffer(); + rdr.read(buf); + while (buf[0] != '>') { + bldr.append(buf[0]); + rdr.read(buf); + } + return bldr.toString(); + } + + + public static boolean charSetImpliesAscii(String charset) { + return charset.equals("ISO-8859-1") || charset.equals("US-ASCII"); + } + + + /** + * Converts the raw characters to XML escapeUrlParam characters. + * + * @param rawContent + * @param charset Null when charset is not known, so we assume it's unicode + * @param isNoLines + * @return escapeUrlParam string + */ + public static String escapeXML(String rawContent, String charset, boolean isNoLines) { + if (rawContent == null) + return ""; + else { + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < rawContent.length(); i++) { + char ch = rawContent.charAt(i); + if (ch == '\'') + sb.append("'"); + else if (ch == '&') + sb.append("&"); + else if (ch == '"') + sb.append("""); + else if (ch == '<') + sb.append("<"); + else if (ch == '>') + sb.append(">"); + else if (ch > '~' && charset != null && charSetImpliesAscii(charset)) + // TODO - why is hashcode the only way to get the unicode number for the character + // in jre 5.0? + sb.append("&#x"+Integer.toHexString(ch).toUpperCase()+";"); + else if (isNoLines) { + if (ch == '\r') + sb.append(" "); + else if (ch != '\n') + sb.append(ch); + } + else + sb.append(ch); + } + return sb.toString(); + } + } + + public static Element getFirstChild(Element e) { + if (e == null) + return null; + Node n = e.getFirstChild(); + while (n != null && n.getNodeType() != Node.ELEMENT_NODE) + n = n.getNextSibling(); + return (Element) n; + } + + public static Element getNamedChild(Element e, String name) { + Element c = getFirstChild(e); + while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName())) + c = getNextSibling(c); + return c; + } + + public static Element getNextSibling(Element e) { + Node n = e.getNextSibling(); + while (n != null && n.getNodeType() != Node.ELEMENT_NODE) + n = n.getNextSibling(); + return (Element) n; + } + + public static void getNamedChildren(Element e, String name, List set) { + Element c = getFirstChild(e); + while (c != null) { + if (name.equals(c.getLocalName()) || name.equals(c.getNodeName()) ) + set.add(c); + c = getNextSibling(c); + } + } + + public static String htmlToXmlEscapedPlainText(Element r) { + StringBuilder s = new StringBuilder(); + Node n = r.getFirstChild(); + boolean ws = false; + while (n != null) { + if (n.getNodeType() == Node.TEXT_NODE) { + String t = n.getTextContent().trim(); + if (Utilities.noString(t)) + ws = true; + else { + if (ws) + s.append(" "); + ws = false; + s.append(t); + } + } + if (n.getNodeType() == Node.ELEMENT_NODE) { + if (ws) + s.append(" "); + ws = false; + s.append(htmlToXmlEscapedPlainText((Element) n)); + if (r.getNodeName().equals("br") || r.getNodeName().equals("p")) + s.append("\r\n"); + } + n = n.getNextSibling(); + } + return s.toString(); + } + + public static String htmlToXmlEscapedPlainText(String definition) throws ParserConfigurationException, SAXException, IOException { + return htmlToXmlEscapedPlainText(parseToDom("
    "+definition+"
    ").getDocumentElement()); + } + + public static String elementToString(Element el) { + if (el == null) + return ""; + Document document = el.getOwnerDocument(); + DOMImplementationLS domImplLS = (DOMImplementationLS) document + .getImplementation(); + LSSerializer serializer = domImplLS.createLSSerializer(); + return serializer.writeToString(el); + } + + public static String getNamedChildValue(Element element, String name) { + Element e = getNamedChild(element, name); + return e == null ? null : e.getAttribute("value"); + } + + public static void setNamedChildValue(Element element, String name, String value) throws FHIRException { + Element e = getNamedChild(element, name); + if (e == null) + throw new FHIRException("unable to find element "+name); + e.setAttribute("value", value); + } + + + public static void getNamedChildrenWithWildcard(Element focus, String name, List children) { + Element c = getFirstChild(focus); + while (c != null) { + String n = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); + if (name.equals(n) || (name.endsWith("[x]") && n.startsWith(name.substring(0, name.length()-3)))) + children.add(c); + c = getNextSibling(c); + } + } + + public static void getNamedChildrenWithTails(Element focus, String name, List children, Set typeTails) { + Element c = getFirstChild(focus); + while (c != null) { + String n = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); + if (n.equals(name) || (!n.equals("responseCode") && (n.startsWith(name) && typeTails.contains(n.substring(name.length()))))) + children.add(c); + c = getNextSibling(c); + } + } + + public static boolean hasNamedChild(Element e, String name) { + Element c = getFirstChild(e); + while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName())) + c = getNextSibling(c); + return c != null; + } + + public static Document parseToDom(String content) throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(new ByteArrayInputStream(content.getBytes())); + } + + public static Document parseFileToDom(String filename) throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(new FileInputStream(filename)); + } + + public static Element getLastChild(Element e) { + if (e == null) + return null; + Node n = e.getLastChild(); + while (n != null && n.getNodeType() != Node.ELEMENT_NODE) + n = n.getPreviousSibling(); + return (Element) n; + } + + public static Element getPrevSibling(Element e) { + Node n = e.getPreviousSibling(); + while (n != null && n.getNodeType() != Node.ELEMENT_NODE) + n = n.getPreviousSibling(); + return (Element) n; + } + + public static String getNamedChildAttribute(Element element, String name, String aname) { + Element e = getNamedChild(element, name); + return e == null ? null : e.getAttribute(aname); + } + + public static void writeDomToFile(Document doc, String filename) throws TransformerException { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource source = new DOMSource(doc); + StreamResult streamResult = new StreamResult(new File(filename)); + transformer.transform(source, streamResult); + } + + public static String getXsiType(org.w3c.dom.Element element) { + Attr a = element.getAttributeNodeNS("http://www.w3.org/2001/XMLSchema-instance", "type"); + return (a == null ? null : a.getTextContent()); + + } + + public static String getDirectText(org.w3c.dom.Element node) { + Node n = node.getFirstChild(); + StringBuilder b = new StringBuilder(); + while (n != null) { + if (n.getNodeType() == Node.TEXT_NODE) + b.append(n.getTextContent()); + n = n.getNextSibling(); + } + return b.toString().trim(); + } + + +} diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/GraphQLDstu3ProviderTest.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/GraphQLDstu3ProviderTest.java index b8da72f1894..f9668d839b4 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/GraphQLDstu3ProviderTest.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/GraphQLDstu3ProviderTest.java @@ -57,7 +57,7 @@ public class GraphQLDstu3ProviderTest { @Ignore public void testGraphInstance() throws Exception { String query = "{name{family,given}}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); @@ -84,7 +84,7 @@ public class GraphQLDstu3ProviderTest { @Ignore public void testGraphInstanceWithFhirpath() throws Exception { String query = "{name(fhirpath:\"family.exists()\"){text,given,family}}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); @@ -109,7 +109,7 @@ public class GraphQLDstu3ProviderTest { @org.junit.Ignore public void testGraphSystemInstance() throws Exception { String query = "{Patient(id:123){id,name{given,family}}}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); @@ -138,7 +138,7 @@ public class GraphQLDstu3ProviderTest { @Ignore public void testGraphSystemList() throws Exception { String query = "{PatientList(name:\"pet\"){name{family,given}}}"; - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escape(query)); + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam(query)); CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 4cf2c689611..26188e4c9ab 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -11,6 +11,12 @@ Fix a crash in JPA server when performing a recursive _include
    ]]> which doesn't actually find any matches. + + When encoding URL parameter values, HAPI FHIR would incorrectly escape + a space (" ") as a plus ("+") insetad of as "%20" as required by + RFC 3986. This affects client calls, as well as URLs generated by + the server (e.g. REST HOOK calls). Thanks to James Daily for reporting! + From 863c4b370ca0606d9386510dc723368bd0d64ba5 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 25 Nov 2017 20:32:42 -0500 Subject: [PATCH 39/39] Add some test debug logs --- .../jpa/search/SearchCoordinatorSvcImpl.java | 10 ++- .../uhn/fhir/jpa/config/TestDstu2Config.java | 2 +- .../uhn/fhir/jpa/config/TestDstu3Config.java | 2 +- .../ca/uhn/fhir/jpa/config/TestR4Config.java | 2 +- .../java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java | 75 +++++++++++-------- .../dao/dstu2/FhirResourceDaoDstu2Test.java | 10 ++- ...rResourceDaoDstu3SearchPageExpiryTest.java | 35 +++++---- 7 files changed, 82 insertions(+), 54 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 6f2a727e59e..340e258f617 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -593,6 +593,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { shouldSync = true; } + // If no abort was requested, bail out + Validate.isTrue(myAbortRequested == false, "Abort has been requested"); + if (shouldSync) { saveUnsynced(theResultIterator); } @@ -605,10 +608,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } } - // Check if an abort got requested - Validate.isTrue(myAbortRequested == false, "Abort has been requested"); - } + + // If no abort was requested, bail out + Validate.isTrue(myAbortRequested == false, "Abort has been requested"); + saveUnsynced(theResultIterator); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java index 49c9107ce28..0017646f79e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java @@ -30,7 +30,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { public DataSource dataSource() { BasicDataSource retVal = new BasicDataSource(); retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver()); - retVal.setUrl("jdbc:derby:memory:myUnitTestDB;create=true"); + retVal.setUrl("jdbc:derby:memory:myUnitTestDBDstu2;create=true"); retVal.setUsername(""); retVal.setPassword(""); return retVal; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index 46fb60ba025..4c7420a3c1d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -90,7 +90,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { }; retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver()); - retVal.setUrl("jdbc:derby:memory:myUnitTestDB;create=true"); + retVal.setUrl("jdbc:derby:memory:myUnitTestDBDstu3;create=true"); retVal.setMaxWaitMillis(10000); retVal.setUsername(""); retVal.setPassword(""); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index d40ea5d2284..c84e1f4748a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -85,7 +85,7 @@ public class TestR4Config extends BaseJavaConfigR4 { }; retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver()); - retVal.setUrl("jdbc:derby:memory:myUnitTestDB;create=true"); + retVal.setUrl("jdbc:derby:memory:myUnitTestDBR4;create=true"); retVal.setMaxWaitMillis(10000); retVal.setUsername(""); retVal.setPassword(""); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 468845de9a4..130fc65d85c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -1,38 +1,12 @@ package ca.uhn.fhir.jpa.dao; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.InputStream; -import java.sql.SQLException; -import java.util.*; -import java.util.concurrent.Callable; - -import javax.persistence.EntityManager; - -import ca.uhn.fhir.jpa.util.StopWatch; -import ca.uhn.fhir.model.dstu2.resource.Observation; -import org.apache.commons.io.IOUtils; -import org.hibernate.search.jpa.Search; -import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.dstu3.model.Resource; -import org.hl7.fhir.instance.model.api.*; -import org.junit.AfterClass; -import org.junit.Before; -import org.mockito.Mockito; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionTemplate; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.term.VersionIndependentConcept; +import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -41,6 +15,31 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.io.IOUtils; +import org.hibernate.search.jpa.Search; +import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.AfterClass; +import org.junit.Before; +import org.mockito.Mockito; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.EntityManager; +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.Callable; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public abstract class BaseJpaTest { @@ -53,13 +52,23 @@ public abstract class BaseJpaTest { public void beforeCreateSrd() { mySrd = mock(ServletRequestDetails.class, Mockito.RETURNS_DEEP_STUBS); when(mySrd.getRequestOperationCallback()).thenReturn(myRequestOperationCallback); - myServerInterceptorList = new ArrayList(); + myServerInterceptorList = new ArrayList<>(); when(mySrd.getServer().getInterceptors()).thenReturn(myServerInterceptorList); - when(mySrd.getUserData()).thenReturn(new HashMap()); + when(mySrd.getUserData()).thenReturn(new HashMap<>()); } protected abstract FhirContext getContext(); + /** + * Sleep until at least 1 ms has elapsed + */ + public void sleepUntilTimeChanges() throws InterruptedException { + StopWatch sw = new StopWatch(); + while (sw.getMillis() == 0) { + Thread.sleep(10); + } + } + protected org.hl7.fhir.dstu3.model.Bundle toBundle(IBundleProvider theSearch) { org.hl7.fhir.dstu3.model.Bundle bundle = new org.hl7.fhir.dstu3.model.Bundle(); for (IBaseResource next : theSearch.getResources(0, theSearch.size())) { @@ -76,11 +85,11 @@ public abstract class BaseJpaTest { return bundle; } - @SuppressWarnings({ "rawtypes" }) + @SuppressWarnings({"rawtypes"}) protected List toList(IBundleProvider theSearch) { return theSearch.getResources(0, theSearch.size()); } - + protected List toUnqualifiedIdValues(IBaseBundle theFound) { List retVal = new ArrayList(); @@ -214,9 +223,9 @@ public abstract class BaseJpaTest { } public static void purgeDatabase(final EntityManager entityManager, PlatformTransactionManager theTxManager, ISearchParamPresenceSvc theSearchParamPresenceSvc, ISearchCoordinatorSvc theSearchCoordinatorSvc, ISearchParamRegistry theSearchParamRegistry) { - + theSearchCoordinatorSvc.cancelAllActiveSearches(); - + TransactionTemplate txTemplate = new TransactionTemplate(theTxManager); txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED); txTemplate.execute(new TransactionCallback() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java index f725737ef79..ac5777116c2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java @@ -2346,7 +2346,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { } @Test - public void testSortByLastUpdated() { + public void testSortByLastUpdated() throws InterruptedException { String methodName = "testSortByLastUpdated"; Patient p = new Patient(); @@ -2354,21 +2354,29 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { p.addName().addFamily(methodName); IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + sleepUntilTimeChanges(); + p = new Patient(); p.addIdentifier().setSystem("urn:system2").setValue(methodName); p.addName().addFamily(methodName); IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + sleepUntilTimeChanges(); + p = new Patient(); p.addIdentifier().setSystem("urn:system3").setValue(methodName); p.addName().addFamily(methodName); IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + sleepUntilTimeChanges(); + p = new Patient(); p.addIdentifier().setSystem("urn:system4").setValue(methodName); p.addName().addFamily(methodName); IIdType id4 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + sleepUntilTimeChanges(); + SearchParameterMap pm; List actual; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchPageExpiryTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchPageExpiryTest.java index ea2fd0c1158..f70f9a81f3d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchPageExpiryTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchPageExpiryTest.java @@ -1,14 +1,17 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.*; - +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; import ca.uhn.fhir.jpa.util.StopWatch; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.StringParam; import org.apache.commons.lang3.Validate; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.test.util.AopTestUtils; @@ -16,10 +19,11 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; -import ca.uhn.fhir.jpa.dao.SearchParameterMap; -import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.StringParam; +import java.util.concurrent.atomic.AtomicLong; + +import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test { private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoDstu3SearchPageExpiryTest.class); @@ -104,14 +108,17 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test { // Search just got used so it shouldn't be deleted myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); + final AtomicLong search3timestamp = new AtomicLong(); newTxTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus theArg0) { - assertNotNull(mySearchEntityDao.findByUuid(searchUuid3)); + Search search3 = mySearchEntityDao.findByUuid(searchUuid3); + assertNotNull(search3); + search3timestamp.set(search3.getCreated().getTime()); } }); - StaleSearchDeletingSvcImpl.setNowForUnitTests(start + 1400); + StaleSearchDeletingSvcImpl.setNowForUnitTests(search3timestamp.get() + 1400); myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); newTxTemplate().execute(new TransactionCallbackWithoutResult() { @@ -127,14 +134,14 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test { } }); - StaleSearchDeletingSvcImpl.setNowForUnitTests(start + 2200); + StaleSearchDeletingSvcImpl.setNowForUnitTests(search3timestamp.get() + 2200); myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); newTxTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus theArg0) { - assertNull(mySearchEntityDao.findByUuid(searchUuid1)); - assertNull(mySearchEntityDao.findByUuid(searchUuid3)); + assertNull("Search 1 still exists", mySearchEntityDao.findByUuid(searchUuid1)); + assertNull("Search 3 still exists", mySearchEntityDao.findByUuid(searchUuid3)); } });