diff --git a/examples/src/main/java/example/ValidatorExamples.java b/examples/src/main/java/example/ValidatorExamples.java index 2991c557ebf..db6d17e5d5d 100644 --- a/examples/src/main/java/example/ValidatorExamples.java +++ b/examples/src/main/java/example/ValidatorExamples.java @@ -176,13 +176,13 @@ public class ValidatorExamples { IValidationSupport valSupport = new IValidationSupport() { @Override - public CodeValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay) { + public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { // TODO: Implement return null; } @Override - public boolean isCodeSystemSupported(String theSystem) { + public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { // TODO: Implement return false; } @@ -194,13 +194,13 @@ public class ValidatorExamples { } @Override - public ValueSet fetchCodeSystem(String theSystem) { + public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) { // TODO: Implement return null; } @Override - public ValueSetExpansionComponent expandValueSet(ConceptSetComponent theInclude) { + public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { // TODO: Implement return null; } diff --git a/hapi-fhir-cli/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupport.java b/hapi-fhir-cli/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupport.java index 85d92f1addd..d471fd36d10 100644 --- a/hapi-fhir-cli/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupport.java +++ b/hapi-fhir-cli/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupport.java @@ -16,12 +16,12 @@ public class LoadingValidationSupport implements IValidationSupport { private static FhirContext myCtx = FhirContext.forDstu2Hl7Org(); @Override - public ValueSetExpansionComponent expandValueSet(ConceptSetComponent theInclude) { + public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { return null; } @Override - public ValueSet fetchCodeSystem(String theSystem) { + public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) { return null; } @@ -46,12 +46,12 @@ public class LoadingValidationSupport implements IValidationSupport { } @Override - public boolean isCodeSystemSupported(String theSystem) { + public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { return false; } @Override - public CodeValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay) { + public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { return null; } diff --git a/hapi-fhir-jpaserver-base/.classpath b/hapi-fhir-jpaserver-base/.classpath index 244640932a4..845e0284061 100644 --- a/hapi-fhir-jpaserver-base/.classpath +++ b/hapi-fhir-jpaserver-base/.classpath @@ -1,21 +1,21 @@ - + - - + + - - - + + + @@ -32,5 +32,5 @@ - + diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java index aeb2a65ffe7..f153e06adf5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.dao; +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2015 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.Collections; import javax.servlet.http.HttpServletRequest; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java index 954a07f91b2..9a0ea48058e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.provider; +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2015 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.dstu2.resource.Patient; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionWebsocketHandler.java index 6f2f3a83c40..9b8177fe649 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionWebsocketHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionWebsocketHandler.java @@ -32,6 +32,7 @@ import org.apache.http.client.utils.URLEncodedUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.TaskScheduler; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; @@ -52,6 +53,10 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; public class SubscriptionWebsocketHandler extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketHandler.class); + @Autowired + @Qualifier("myFhirContextDstu2") + private FhirContext myCtx; + private ScheduledFuture myScheduleFuture; private IState myState = new InitialState(); @@ -88,6 +93,12 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement myState.handleTextMessage(theSession, theMessage); } + @Override + public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception { + super.handleTransportError(theSession, theException); + ourLog.error("Transport error", theException); + } + @PostConstruct public void postConstruct() { ourLog.info("Creating scheduled task for subscription websocket connection"); @@ -119,54 +130,26 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement } } - private class BoundStaticSubscipriptionState implements IState { - - private WebSocketSession mySession; - - public BoundStaticSubscipriptionState(WebSocketSession theSession) { - mySession = theSession; - } - - @Override - public void deliver(List theResults) { - try { - String payload = "ping " + mySubscriptionId.getIdPart(); - ourLog.info("Sending WebSocket message: {}", payload); - mySession.sendMessage(new TextMessage(payload)); - } catch (IOException e) { - handleFailure(e); - } - } - - @Override - public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { - try { - theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload())); - } catch (IOException e) { - handleFailure(e); - } - } - - @Override - public void closing() { - // nothing - } - - } - - @Autowired - private FhirContext myCtx; - private class BoundDynamicSubscriptionState implements IState { - private WebSocketSession mySession; private EncodingEnum myEncoding; + private WebSocketSession mySession; public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) { mySession = theSession; myEncoding = theEncoding; } + @Override + public void closing() { + ourLog.info("Deleting subscription {}", mySubscriptionId); + try { + mySubscriptionDao.delete(mySubscriptionId); + } catch (Exception e) { + handleFailure(e); + } + } + @Override public void deliver(List theResults) { try { @@ -190,12 +173,37 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement } } + } + + private class BoundStaticSubscipriptionState implements IState { + + private WebSocketSession mySession; + + public BoundStaticSubscipriptionState(WebSocketSession theSession) { + mySession = theSession; + } + @Override public void closing() { - ourLog.info("Deleting subscription {}", mySubscriptionId); + // nothing + } + + @Override + public void deliver(List theResults) { try { - mySubscriptionDao.delete(mySubscriptionId); - } catch (Exception e) { + String payload = "ping " + mySubscriptionId.getIdPart(); + ourLog.info("Sending WebSocket message: {}", payload); + mySession.sendMessage(new TextMessage(payload)); + } catch (IOException e) { + handleFailure(e); + } + } + + @Override + public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { + try { + theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload())); + } catch (IOException e) { handleFailure(e); } } @@ -204,34 +212,37 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement private class InitialState implements IState { - @Override - public void deliver(List theResults) { - throw new IllegalStateException(); - } - - @Override - public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { - String message = theMessage.getPayload(); - if (message.startsWith("bind ")) { - String remaining = message.substring("bind ".length()); - - IIdType subscriptionId; - if (remaining.contains("?")) { - subscriptionId = bingSearch(theSession, remaining); - } else { - subscriptionId = bindSimple(theSession, remaining); - if (subscriptionId == null) { - return; - } - } + private IIdType bindSimple(WebSocketSession theSession, String theBindString) { + IdDt id = new IdDt(theBindString); + if (!id.hasIdPart() || !id.isIdPartValid()) { try { - theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart())); + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included")); } catch (IOException e) { handleFailure(e); } - + return null; } + + if (id.hasResourceType() == false) { + id = id.withResourceType("Subscription"); + } + + try { + Subscription subscription = mySubscriptionDao.read(id); + mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id); + mySubscriptionId = subscription.getIdElement(); + myState = new BoundStaticSubscipriptionState(theSession); + } catch (ResourceNotFoundException e) { + try { + theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - Unknown subscription: " + id.getValue())); + } catch (IOException e1) { + handleFailure(e); + } + return null; + } + + return id; } private IIdType bingSearch(WebSocketSession theSession, String theRemaining) { @@ -278,54 +289,51 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement return null; } - private IIdType bindSimple(WebSocketSession theSession, String theBindString) { - IdDt id = new IdDt(theBindString); - - if (!id.hasIdPart() || !id.isIdPartValid()) { - try { - theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included")); - } catch (IOException e) { - handleFailure(e); - } - return null; - } - - if (id.hasResourceType() == false) { - id = id.withResourceType("Subscription"); - } - - try { - Subscription subscription = mySubscriptionDao.read(id); - mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id); - mySubscriptionId = subscription.getIdElement(); - myState = new BoundStaticSubscipriptionState(theSession); - } catch (ResourceNotFoundException e) { - try { - theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - Unknown subscription: " + id.getValue())); - } catch (IOException e1) { - handleFailure(e); - } - return null; - } - - return id; - } - @Override public void closing() { // nothing } + @Override + public void deliver(List theResults) { + throw new IllegalStateException(); + } + + @Override + public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) { + String message = theMessage.getPayload(); + if (message.startsWith("bind ")) { + String remaining = message.substring("bind ".length()); + + IIdType subscriptionId; + if (remaining.contains("?")) { + subscriptionId = bingSearch(theSession, remaining); + } else { + subscriptionId = bindSimple(theSession, remaining); + if (subscriptionId == null) { + return; + } + } + + try { + theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart())); + } catch (IOException e) { + handleFailure(e); + } + + } + } + } private interface IState { - void deliver(List theResults); - void closing(); + void deliver(List theResults); + void handleTextMessage(WebSocketSession theSession, TextMessage theMessage); } -} \ No newline at end of file +} diff --git a/hapi-fhir-jpaserver-base/src/main/resources/fhir-spring-subscription-config-dstu2.xml b/hapi-fhir-jpaserver-base/src/main/resources/fhir-spring-subscription-config-dstu2.xml index 830235c6601..f276526efe6 100644 --- a/hapi-fhir-jpaserver-base/src/main/resources/fhir-spring-subscription-config-dstu2.xml +++ b/hapi-fhir-jpaserver-base/src/main/resources/fhir-spring-subscription-config-dstu2.xml @@ -4,10 +4,12 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:websocket="http://www.springframework.org/schema/websocket" xmlns:task="http://www.springframework.org/schema/task" + xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> @@ -26,4 +28,6 @@ + + \ No newline at end of file diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index c3208cbd422..1c8dbf59879 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -42,6 +42,13 @@ war provided + + ca.uhn.hapi.fhir + hapi-fhir-testpage-overlay + 1.3-SNAPSHOT + classes + provided + com.phloc diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/mvc/SubscriptionPlaygroundController.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/mvc/SubscriptionPlaygroundController.java new file mode 100644 index 00000000000..993f5f87cc7 --- /dev/null +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/mvc/SubscriptionPlaygroundController.java @@ -0,0 +1,27 @@ +package ca.uhn.fhirtest.mvc; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; + +import ca.uhn.fhir.to.BaseController; +import ca.uhn.fhir.to.model.HomeRequest; + +@org.springframework.stereotype.Controller() +public class SubscriptionPlaygroundController extends BaseController { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionPlaygroundController.class); + + @RequestMapping(value = { "/subscriptions" }) + public String subscriptionsHome(final HttpServletRequest theServletRequest, HomeRequest theRequest, final ModelMap theModel) { + addCommonParams(theServletRequest, theRequest, theModel); + + theModel.put("notHome", true); + theModel.put("extraBreadcrumb", "Subscriptions"); + + ourLog.info(logPrefix(theModel) + "Displayed subscriptions playground page"); + + return "subscriptions"; + } + +} diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml index 13c6c5fdad2..e8bf266e1e3 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml @@ -51,9 +51,9 @@ - + - + diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml index 95fbacb6f31..e2c5b8bd08e 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml @@ -38,9 +38,9 @@ - + - + diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/logback.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/logback.xml index 8c9e711d605..652e8e884b3 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/logback.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/logback.xml @@ -43,6 +43,13 @@ + + + + + + + diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config-extracontrollers.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config-extracontrollers.xml new file mode 100644 index 00000000000..ca797717fa2 --- /dev/null +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config-extracontrollers.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml index 11518aa3b97..7359ecf0769 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml @@ -34,6 +34,8 @@ + + diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/subscriptions.html b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/subscriptions.html new file mode 100644 index 00000000000..1d06bee5745 --- /dev/null +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/subscriptions.html @@ -0,0 +1,133 @@ + + + +RESTful Tester + + + +
+ + +
+ +
+
+ +
+ +
+ +
+ + + + + +
+
+

Subscriptions Playground

+
+
+
+

+ This page is a test playground for WebSocket Subscriptions +

+
+
+
+ + + + + +
+
...1
+ + +
+ + +
+
+ Enter a criteria in the text box below and then click subscribe to + create a dynamic subscription and then display the results as they + arrive. +
+
+ +
+
+
+
+ Criteria +
+ +
+
+
+ +
+
+ +
+
+ + + +
+
Subscription Results
+
+

+ This will be status... +

+
+ + +
    +
  • Cras justo odio
  • +
  • Dapibus ac facilisis in
  • +
  • Morbi leo risus
  • +
  • Porta ac consectetur ac
  • +
  • Vestibulum at eros
  • +
+
+
+ + +
+
+ + +
+ +
+ + diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java b/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java index 9de1bdc81a6..62f21ce5789 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java @@ -67,7 +67,7 @@ public class UhnFhirTestApp { client.create(p1); List resources = ctx.newJsonParser().parseBundle(IOUtils.toString(UhnFhirTestApp.class.getResourceAsStream("/bundle.json"))).toListOfResources(); - client.transaction().withResources(resources).execute(); +// client.transaction().withResources(resources).execute(); // for (int i = 0; i < 1000; i++) { // diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index 595ac70b4ed..ba763222506 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -44,11 +44,7 @@ hapi-fhir-structures-dstu2 1.3-SNAPSHOT - + org.thymeleaf thymeleaf @@ -71,7 +67,7 @@ logback-classic test - + commons-dbcp @@ -202,6 +198,13 @@ false + + org.apache.maven.plugins + maven-war-plugin + + true + + diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java new file mode 100644 index 00000000000..3533f7c7536 --- /dev/null +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java @@ -0,0 +1,637 @@ +package ca.uhn.fhir.to; + +import static org.apache.commons.lang3.StringUtils.defaultString; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.HttpEntityWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.ui.ModelMap; +import org.thymeleaf.TemplateEngine; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.BundleEntry; +import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Conformance; +import ca.uhn.fhir.model.dstu.resource.Conformance.Rest; +import ca.uhn.fhir.model.primitive.DecimalDt; +import ca.uhn.fhir.narrative.INarrativeGenerator; +import ca.uhn.fhir.rest.client.GenericClient; +import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.to.model.HomeRequest; +import ca.uhn.fhir.util.ExtensionConstants; + +public class BaseController { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseController.class); + static final String PARAM_RESOURCE = "resource"; + static final String RESOURCE_COUNT_EXT_URL = "http://hl7api.sourceforge.net/hapi-fhir/res/extdefs.html#resourceCount"; + + @Autowired + protected TesterConfig myConfig; + private Map myContexts = new HashMap(); + private List myFilterHeaders; + @Autowired + private TemplateEngine myTemplateEngine; + + public BaseController() { + super(); + } + + protected IResource addCommonParams(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) { + if (myConfig.getDebugTemplatesMode()) { + myTemplateEngine.getCacheManager().clearAllCaches(); + } + + final String serverId = theRequest.getServerIdWithDefault(myConfig); + final String serverBase = theRequest.getServerBase(theServletRequest, myConfig); + final String serverName = theRequest.getServerName(myConfig); + theModel.put("serverId", serverId); + theModel.put("base", serverBase); + theModel.put("baseName", serverName); + theModel.put("resourceName", defaultString(theRequest.getResource())); + theModel.put("encoding", theRequest.getEncoding()); + theModel.put("pretty", theRequest.getPretty()); + theModel.put("_summary", theRequest.get_summary()); + theModel.put("serverEntries", myConfig.getIdToServerName()); + + return loadAndAddConf(theServletRequest, theRequest, theModel); + } + + private Header[] applyHeaderFilters(Header[] theAllHeaders) { + if (myFilterHeaders == null || myFilterHeaders.isEmpty()) { + return theAllHeaders; + } + ArrayList
retVal = new ArrayList
(); + for (Header next : theAllHeaders) { + if (!myFilterHeaders.contains(next.getName().toLowerCase())) { + retVal.add(next); + } + } + return retVal.toArray(new Header[retVal.size()]); + } + + private String format(String theResultBody, EncodingEnum theEncodingEnum) { + String str = StringEscapeUtils.escapeHtml4(theResultBody); + if (str == null || theEncodingEnum == null) { + return str; + } + + StringBuilder b = new StringBuilder(); + + if (theEncodingEnum == EncodingEnum.JSON) { + + boolean inValue = false; + boolean inQuote = false; + for (int i = 0; i < str.length(); i++) { + char prevChar = (i > 0) ? str.charAt(i - 1) : ' '; + char nextChar = str.charAt(i); + char nextChar2 = (i + 1) < str.length() ? str.charAt(i + 1) : ' '; + char nextChar3 = (i + 2) < str.length() ? str.charAt(i + 2) : ' '; + char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' '; + char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' '; + char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' '; + if (inQuote) { + b.append(nextChar); + if (prevChar != '\\' && nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { + b.append("quot;"); + i += 5; + inQuote = false; + } else if (nextChar == '\\' && nextChar2 == '"') { + b.append("quot;"); + i += 5; + inQuote = false; + } + } else { + if (nextChar == ':') { + inValue = true; + b.append(nextChar); + } else if (nextChar == '[' || nextChar == '{') { + b.append(""); + b.append(nextChar); + b.append(""); + inValue = false; + } else if (nextChar == '}' || nextChar == '}' || nextChar == ',') { + b.append(""); + b.append(nextChar); + b.append(""); + inValue = false; + } else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { + if (inValue) { + b.append("""); + } else { + b.append("""); + } + inQuote = true; + i += 5; + } else if (nextChar == ':') { + b.append(""); + b.append(nextChar); + b.append(""); + inValue = true; + } else { + b.append(nextChar); + } + } + } + + } else { + boolean inQuote = false; + boolean inTag = false; + for (int i = 0; i < str.length(); i++) { + char nextChar = str.charAt(i); + char nextChar2 = (i + 1) < str.length() ? str.charAt(i + 1) : ' '; + char nextChar3 = (i + 2) < str.length() ? str.charAt(i + 2) : ' '; + char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' '; + char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' '; + char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' '; + if (inQuote) { + b.append(nextChar); + if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { + b.append("quot;"); + i += 5; + inQuote = false; + } + } else if (inTag) { + if (nextChar == '&' && nextChar2 == 'g' && nextChar3 == 't' && nextChar4 == ';') { + b.append(">"); + inTag = false; + i += 3; + } else if (nextChar == ' ') { + b.append(""); + b.append(nextChar); + } else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { + b.append("""); + inQuote = true; + i += 5; + } else { + b.append(nextChar); + } + } else { + if (nextChar == '&' && nextChar2 == 'l' && nextChar3 == 't' && nextChar4 == ';') { + b.append("<"); + inTag = true; + i += 3; + } else { + b.append(nextChar); + } + } + } + } + + return b.toString(); + } + + private String formatUrl(String theUrlBase, String theResultBody) { + String str = theResultBody; + if (str == null) { + return str; + } + + try { + str = URLDecoder.decode(str, "UTF-8"); + } catch (UnsupportedEncodingException e) { + ourLog.error("Should not happen", e); + } + + StringBuilder b = new StringBuilder(); + b.append(""); + + boolean inParams = false; + for (int i = 0; i < str.length(); i++) { + char nextChar = str.charAt(i); + // char nextChar2 = i < str.length()-2 ? str.charAt(i+1):' '; + // char nextChar3 = i < str.length()-2 ? str.charAt(i+2):' '; + if (!inParams) { + if (nextChar == '?') { + inParams = true; + b.append("?"); + } else { + if (i == theUrlBase.length()) { + b.append(""); + } + b.append(nextChar); + } + } else { + if (nextChar == '&') { + b.append("&"); + } else if (nextChar == '=') { + b.append("="); + // }else if (nextChar=='%' && Character.isLetterOrDigit(nextChar2)&& Character.isLetterOrDigit(nextChar3)) { + // URLDecoder.decode(s, enc) + } else { + b.append(nextChar); + } + } + } + + if (inParams) { + b.append(""); + } + return b.toString(); + } + + protected FhirContext getContext(HomeRequest theRequest) { + FhirVersionEnum version = theRequest.getFhirVersion(myConfig); + FhirContext retVal = myContexts.get(version); + if (retVal == null) { + retVal = new FhirContext(version); + myContexts.put(version, retVal); + } + return retVal; + } + + protected RuntimeResourceDefinition getResourceType(HomeRequest theRequest, HttpServletRequest theReq) throws ServletException { + String resourceName = StringUtils.defaultString(theReq.getParameter(PARAM_RESOURCE)); + RuntimeResourceDefinition def = getContext(theRequest).getResourceDefinition(resourceName); + if (def == null) { + throw new ServletException("Invalid resourceName: " + resourceName); + } + return def; + } + + protected ResultType handleClientException(GenericClient theClient, Exception e, ModelMap theModel) { + ResultType returnsResource; + returnsResource = ResultType.NONE; + ourLog.warn("Failed to invoke server", e); + + if (theClient.getLastResponse() == null) { + theModel.put("errorMsg", "Error: " + e.getMessage()); + } + + return returnsResource; + } + + private IResource loadAndAddConf(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) { + switch (theRequest.getFhirVersion(myConfig)) { + case DEV: + return loadAndAddConfDstu2(theServletRequest, theRequest, theModel); + case DSTU1: + return loadAndAddConfDstu1(theServletRequest, theRequest, theModel); + case DSTU2: + return loadAndAddConfDstu2(theServletRequest, theRequest, theModel); + } + throw new IllegalStateException("Unknown version: " + theRequest.getFhirVersion(myConfig)); + } + + private Conformance loadAndAddConfDstu1(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) { + CaptureInterceptor interceptor = new CaptureInterceptor(); + GenericClient client = theRequest.newClient(theServletRequest, getContext(theRequest), myConfig, interceptor); + + Conformance conformance; + try { + conformance = (Conformance) client.conformance(); + } catch (Exception e) { + ourLog.warn("Failed to load conformance statement", e); + theModel.put("errorMsg", "Failed to load conformance statement, error was: " + e.toString()); + conformance = new Conformance(); + } + + theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(conformance)); + + Map resourceCounts = new HashMap(); + long total = 0; + for (Rest nextRest : conformance.getRest()) { + for (ca.uhn.fhir.model.dstu.resource.Conformance.RestResource nextResource : nextRest.getResource()) { + List exts = nextResource.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); + if (exts != null && exts.size() > 0) { + Number nextCount = ((DecimalDt) (exts.get(0).getValue())).getValueAsNumber(); + resourceCounts.put(nextResource.getType().getValue(), nextCount); + total += nextCount.longValue(); + } + } + } + theModel.put("resourceCounts", resourceCounts); + + if (total > 0) { + for (Rest nextRest : conformance.getRest()) { + Collections.sort(nextRest.getResource(), new Comparator() { + @Override + public int compare(ca.uhn.fhir.model.dstu.resource.Conformance.RestResource theO1, ca.uhn.fhir.model.dstu.resource.Conformance.RestResource theO2) { + DecimalDt count1 = new DecimalDt(); + List count1exts = theO1.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); + if (count1exts != null && count1exts.size() > 0) { + count1 = (DecimalDt) count1exts.get(0).getValue(); + } + DecimalDt count2 = new DecimalDt(); + List count2exts = theO2.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); + if (count2exts != null && count2exts.size() > 0) { + count2 = (DecimalDt) count2exts.get(0).getValue(); + } + int retVal = count2.compareTo(count1); + if (retVal == 0) { + retVal = theO1.getType().getValue().compareTo(theO2.getType().getValue()); + } + return retVal; + } + }); + } + } + + theModel.put("conf", conformance); + theModel.put("requiredParamExtension", ExtensionConstants.PARAM_IS_REQUIRED); + + return conformance; + } + + private IResource loadAndAddConfDstu2(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) { + CaptureInterceptor interceptor = new CaptureInterceptor(); + GenericClient client = theRequest.newClient(theServletRequest, getContext(theRequest), myConfig, interceptor); + + ca.uhn.fhir.model.dstu2.resource.Conformance conformance; + try { + conformance = (ca.uhn.fhir.model.dstu2.resource.Conformance) client.conformance(); + } catch (Exception e) { + ourLog.warn("Failed to load conformance statement", e); + theModel.put("errorMsg", "Failed to load conformance statement, error was: " + e.toString()); + conformance = new ca.uhn.fhir.model.dstu2.resource.Conformance(); + } + + theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(conformance)); + + Map resourceCounts = new HashMap(); + long total = 0; + for (ca.uhn.fhir.model.dstu2.resource.Conformance.Rest nextRest : conformance.getRest()) { + for (ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource nextResource : nextRest.getResource()) { + List exts = nextResource.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); + if (exts != null && exts.size() > 0) { + Number nextCount = ((DecimalDt) (exts.get(0).getValue())).getValueAsNumber(); + resourceCounts.put(nextResource.getTypeElement().getValue(), nextCount); + total += nextCount.longValue(); + } + } + } + theModel.put("resourceCounts", resourceCounts); + + if (total > 0) { + for (ca.uhn.fhir.model.dstu2.resource.Conformance.Rest nextRest : conformance.getRest()) { + Collections.sort(nextRest.getResource(), new Comparator() { + @Override + public int compare(ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource theO1, ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource theO2) { + DecimalDt count1 = new DecimalDt(); + List count1exts = theO1.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); + if (count1exts != null && count1exts.size() > 0) { + count1 = (DecimalDt) count1exts.get(0).getValue(); + } + DecimalDt count2 = new DecimalDt(); + List count2exts = theO2.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); + if (count2exts != null && count2exts.size() > 0) { + count2 = (DecimalDt) count2exts.get(0).getValue(); + } + int retVal = count2.compareTo(count1); + if (retVal == 0) { + retVal = theO1.getTypeElement().getValue().compareTo(theO2.getTypeElement().getValue()); + } + return retVal; + } + }); + } + } + + theModel.put("conf", conformance); + theModel.put("requiredParamExtension", ExtensionConstants.PARAM_IS_REQUIRED); + + return conformance; + } + + protected String logPrefix(ModelMap theModel) { + return "[server=" + theModel.get("serverId") + "] - "; + } + + private String parseNarrative(HomeRequest theRequest, EncodingEnum theCtEnum, String theResultBody) { + try { + IResource resource = (IResource) theCtEnum.newParser(getContext(theRequest)).parseResource(theResultBody); + String retVal = resource.getText().getDiv().getValueAsString(); + return StringUtils.defaultString(retVal); + } catch (Exception e) { + ourLog.error("Failed to parse resource", e); + return ""; + } + } + + protected String preProcessMessageBody(String theBody) { + if (theBody == null) { + return ""; + } + String retVal = theBody.trim(); + + StringBuilder b = new StringBuilder(); + for (int i = 0; i < retVal.length(); i++) { + char nextChar = retVal.charAt(i); + int nextCharI = nextChar; + if (nextCharI == 65533) { + b.append(' '); + continue; + } + if (nextCharI == 160) { + b.append(' '); + continue; + } + if (nextCharI == 194) { + b.append(' '); + continue; + } + b.append(nextChar); + } + retVal = b.toString(); + return retVal; + } + + protected void processAndAddLastClientInvocation(GenericClient theClient, ResultType theResultType, ModelMap theModelMap, long theLatency, String outcomeDescription, CaptureInterceptor theInterceptor, HomeRequest theRequest) { + try { + HttpRequestBase lastRequest = theInterceptor.getLastRequest(); + HttpResponse lastResponse = theInterceptor.getLastResponse(); + String requestBody = null; + String requestUrl = lastRequest != null ? lastRequest.getURI().toASCIIString() : null; + String action = lastRequest != null ? lastRequest.getMethod() : null; + String resultStatus = lastResponse != null ? lastResponse.getStatusLine().toString() : null; + String resultBody = StringUtils.defaultString(theInterceptor.getLastResponseBody()); + + if (lastRequest instanceof HttpEntityEnclosingRequest) { + HttpEntity entity = ((HttpEntityEnclosingRequest) lastRequest).getEntity(); + if (entity.isRepeatable()) { + requestBody = IOUtils.toString(entity.getContent()); + } + } + + ContentType ct = lastResponse != null ? ContentType.get(lastResponse.getEntity()) : null; + String mimeType = ct != null ? ct.getMimeType() : null; + EncodingEnum ctEnum = EncodingEnum.forContentType(mimeType); + String narrativeString = ""; + + StringBuilder resultDescription = new StringBuilder(); + Bundle bundle = null; + + if (ctEnum == null) { + resultDescription.append("Non-FHIR response"); + } else { + switch (ctEnum) { + case JSON: + if (theResultType == ResultType.RESOURCE) { + narrativeString = parseNarrative(theRequest, ctEnum, resultBody); + resultDescription.append("JSON resource"); + } else if (theResultType == ResultType.BUNDLE) { + resultDescription.append("JSON bundle"); + bundle = getContext(theRequest).newJsonParser().parseBundle(resultBody); + } + break; + case XML: + default: + if (theResultType == ResultType.RESOURCE) { + narrativeString = parseNarrative(theRequest, ctEnum, resultBody); + resultDescription.append("XML resource"); + } else if (theResultType == ResultType.BUNDLE) { + resultDescription.append("XML bundle"); + bundle = getContext(theRequest).newXmlParser().parseBundle(resultBody); + } + break; + } + } + + /* + * DSTU2 no longer has a title in the bundle format, but it's still useful here.. + */ + if (bundle != null) { + INarrativeGenerator gen = getContext(theRequest).getNarrativeGenerator(); + if (gen != null) { + for (BundleEntry next : bundle.getEntries()) { + if (next.getTitle().isEmpty() && next.getResource() != null) { + String title = gen.generateTitle(next.getResource()); + next.getTitle().setValue(title); + } + } + } + } + + resultDescription.append(" (").append(resultBody.length() + " bytes)"); + + Header[] requestHeaders = lastRequest != null ? applyHeaderFilters(lastRequest.getAllHeaders()) : new Header[0]; + Header[] responseHeaders = lastResponse != null ? applyHeaderFilters(lastResponse.getAllHeaders()) : new Header[0]; + + theModelMap.put("outcomeDescription", outcomeDescription); + theModelMap.put("resultDescription", resultDescription.toString()); + theModelMap.put("action", action); + theModelMap.put("bundle", bundle); + theModelMap.put("resultStatus", resultStatus); + + theModelMap.put("requestUrl", requestUrl); + theModelMap.put("requestUrlText", formatUrl(theClient.getUrlBase(), requestUrl)); + + String requestBodyText = format(requestBody, ctEnum); + theModelMap.put("requestBody", requestBodyText); + + String resultBodyText = format(resultBody, ctEnum); + theModelMap.put("resultBody", resultBodyText); + + theModelMap.put("resultBodyIsLong", resultBodyText.length() > 1000); + theModelMap.put("requestHeaders", requestHeaders); + theModelMap.put("responseHeaders", responseHeaders); + theModelMap.put("narrative", narrativeString); + theModelMap.put("latencyMs", theLatency); + + } catch (Exception e) { + ourLog.error("Failure during processing", e); + theModelMap.put("errorMsg", "Error during processing: " + e.getMessage()); + } + + } + + public static class CaptureInterceptor implements IClientInterceptor { + + private HttpRequestBase myLastRequest; + private HttpResponse myLastResponse; + private String myResponseBody; + + public HttpRequestBase getLastRequest() { + return myLastRequest; + } + + public HttpResponse getLastResponse() { + return myLastResponse; + } + + public String getLastResponseBody() { + return myResponseBody; + } + + @Override + public void interceptRequest(HttpRequestBase theRequest) { + assert myLastRequest == null; + myLastRequest = theRequest; + } + + @Override + public void interceptResponse(HttpResponse theResponse) throws IOException { + assert myLastResponse == null; + myLastResponse = theResponse; + + HttpEntity respEntity = theResponse.getEntity(); + if (respEntity != null) { + final byte[] bytes; + try { + bytes = IOUtils.toByteArray(respEntity.getContent()); + } catch (IllegalStateException e) { + throw new InternalErrorException(e); + } + + myResponseBody = new String(bytes, "UTF-8"); + theResponse.setEntity(new MyEntityWrapper(respEntity, bytes)); + } + } + + private static class MyEntityWrapper extends HttpEntityWrapper { + + private byte[] myBytes; + + public MyEntityWrapper(HttpEntity theWrappedEntity, byte[] theBytes) { + super(theWrappedEntity); + myBytes = theBytes; + } + + @Override + public InputStream getContent() throws IOException { + return new ByteArrayInputStream(myBytes); + } + + @Override + public void writeTo(OutputStream theOutstream) throws IOException { + theOutstream.write(myBytes); + } + + } + + } + + protected enum ResultType { + BUNDLE, NONE, RESOURCE, TAGLIST + } + +} \ No newline at end of file diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java index 96f05f8c2a6..ab9fc124b97 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java @@ -2,19 +2,10 @@ package ca.uhn.fhir.to; import static org.apache.commons.lang3.StringUtils.*; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.TreeSet; import javax.json.Json; @@ -22,27 +13,13 @@ import javax.json.stream.JsonGenerator; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.HttpEntityWrapper; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; -import org.thymeleaf.TemplateEngine; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.Include; @@ -55,13 +32,10 @@ import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.model.primitive.BoundCodeDt; import ca.uhn.fhir.model.primitive.DateTimeDt; -import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.narrative.INarrativeGenerator; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.client.GenericClient; -import ca.uhn.fhir.rest.client.IClientInterceptor; import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.gclient.ICreateTyped; import ca.uhn.fhir.rest.gclient.IQuery; @@ -73,27 +47,14 @@ import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.rest.gclient.TokenClientParam; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.to.model.HomeRequest; import ca.uhn.fhir.to.model.ResourceRequest; import ca.uhn.fhir.to.model.TransactionRequest; import ca.uhn.fhir.util.ExtensionConstants; @org.springframework.stereotype.Controller() -public class Controller { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Controller.class); - private static final String PARAM_RESOURCE = "resource"; - private static final String RESOURCE_COUNT_EXT_URL = "http://hl7api.sourceforge.net/hapi-fhir/res/extdefs.html#resourceCount"; - - @Autowired - private TesterConfig myConfig; - - private Map myContexts = new HashMap(); - - private List myFilterHeaders; - - @Autowired - private TemplateEngine myTemplateEngine; +public class Controller extends BaseController { + static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Controller.class); @RequestMapping(value = { "/about" }) public String actionAbout(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) { @@ -558,39 +519,6 @@ public class Controller { return "result"; } - private IResource addCommonParams(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) { - if (myConfig.getDebugTemplatesMode()) { - myTemplateEngine.getCacheManager().clearAllCaches(); - } - - final String serverId = theRequest.getServerIdWithDefault(myConfig); - final String serverBase = theRequest.getServerBase(theServletRequest, myConfig); - final String serverName = theRequest.getServerName(myConfig); - theModel.put("serverId", serverId); - theModel.put("base", serverBase); - theModel.put("baseName", serverName); - theModel.put("resourceName", defaultString(theRequest.getResource())); - theModel.put("encoding", theRequest.getEncoding()); - theModel.put("pretty", theRequest.getPretty()); - theModel.put("_summary", theRequest.get_summary()); - theModel.put("serverEntries", myConfig.getIdToServerName()); - - return loadAndAddConf(theServletRequest, theRequest, theModel); - } - - private Header[] applyHeaderFilters(Header[] theAllHeaders) { - if (myFilterHeaders == null || myFilterHeaders.isEmpty()) { - return theAllHeaders; - } - ArrayList
retVal = new ArrayList
(); - for (Header next : theAllHeaders) { - if (!myFilterHeaders.contains(next.getName().toLowerCase())) { - retVal.add(next); - } - } - return retVal.toArray(new Header[retVal.size()]); - } - private void doActionCreateOrValidate(HttpServletRequest theReq, HomeRequest theRequest, BindingResult theBindingResult, ModelMap theModel, String theMethod) { boolean validate = "validate".equals(theMethod); @@ -805,197 +733,6 @@ public class Controller { return haveSearchParams; } - private String format(String theResultBody, EncodingEnum theEncodingEnum) { - String str = StringEscapeUtils.escapeHtml4(theResultBody); - if (str == null || theEncodingEnum == null) { - return str; - } - - StringBuilder b = new StringBuilder(); - - if (theEncodingEnum == EncodingEnum.JSON) { - - boolean inValue = false; - boolean inQuote = false; - for (int i = 0; i < str.length(); i++) { - char prevChar = (i > 0) ? str.charAt(i - 1) : ' '; - char nextChar = str.charAt(i); - char nextChar2 = (i + 1) < str.length() ? str.charAt(i + 1) : ' '; - char nextChar3 = (i + 2) < str.length() ? str.charAt(i + 2) : ' '; - char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' '; - char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' '; - char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' '; - if (inQuote) { - b.append(nextChar); - if (prevChar != '\\' && nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { - b.append("quot;"); - i += 5; - inQuote = false; - } else if (nextChar == '\\' && nextChar2 == '"') { - b.append("quot;"); - i += 5; - inQuote = false; - } - } else { - if (nextChar == ':') { - inValue = true; - b.append(nextChar); - } else if (nextChar == '[' || nextChar == '{') { - b.append(""); - b.append(nextChar); - b.append(""); - inValue = false; - } else if (nextChar == '}' || nextChar == '}' || nextChar == ',') { - b.append(""); - b.append(nextChar); - b.append(""); - inValue = false; - } else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { - if (inValue) { - b.append("""); - } else { - b.append("""); - } - inQuote = true; - i += 5; - } else if (nextChar == ':') { - b.append(""); - b.append(nextChar); - b.append(""); - inValue = true; - } else { - b.append(nextChar); - } - } - } - - } else { - boolean inQuote = false; - boolean inTag = false; - for (int i = 0; i < str.length(); i++) { - char nextChar = str.charAt(i); - char nextChar2 = (i + 1) < str.length() ? str.charAt(i + 1) : ' '; - char nextChar3 = (i + 2) < str.length() ? str.charAt(i + 2) : ' '; - char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' '; - char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' '; - char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' '; - if (inQuote) { - b.append(nextChar); - if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { - b.append("quot;"); - i += 5; - inQuote = false; - } - } else if (inTag) { - if (nextChar == '&' && nextChar2 == 'g' && nextChar3 == 't' && nextChar4 == ';') { - b.append(">"); - inTag = false; - i += 3; - } else if (nextChar == ' ') { - b.append(""); - b.append(nextChar); - } else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') { - b.append("""); - inQuote = true; - i += 5; - } else { - b.append(nextChar); - } - } else { - if (nextChar == '&' && nextChar2 == 'l' && nextChar3 == 't' && nextChar4 == ';') { - b.append("<"); - inTag = true; - i += 3; - } else { - b.append(nextChar); - } - } - } - } - - return b.toString(); - } - - private String formatUrl(String theUrlBase, String theResultBody) { - String str = theResultBody; - if (str == null) { - return str; - } - - try { - str = URLDecoder.decode(str, "UTF-8"); - } catch (UnsupportedEncodingException e) { - ourLog.error("Should not happen", e); - } - - StringBuilder b = new StringBuilder(); - b.append(""); - - boolean inParams = false; - for (int i = 0; i < str.length(); i++) { - char nextChar = str.charAt(i); - // char nextChar2 = i < str.length()-2 ? str.charAt(i+1):' '; - // char nextChar3 = i < str.length()-2 ? str.charAt(i+2):' '; - if (!inParams) { - if (nextChar == '?') { - inParams = true; - b.append("?"); - } else { - if (i == theUrlBase.length()) { - b.append(""); - } - b.append(nextChar); - } - } else { - if (nextChar == '&') { - b.append("&"); - } else if (nextChar == '=') { - b.append("="); - // }else if (nextChar=='%' && Character.isLetterOrDigit(nextChar2)&& Character.isLetterOrDigit(nextChar3)) { - // URLDecoder.decode(s, enc) - } else { - b.append(nextChar); - } - } - } - - if (inParams) { - b.append(""); - } - return b.toString(); - } - - private FhirContext getContext(HomeRequest theRequest) { - FhirVersionEnum version = theRequest.getFhirVersion(myConfig); - FhirContext retVal = myContexts.get(version); - if (retVal == null) { - retVal = new FhirContext(version); - myContexts.put(version, retVal); - } - return retVal; - } - - private RuntimeResourceDefinition getResourceType(HomeRequest theRequest, HttpServletRequest theReq) throws ServletException { - String resourceName = StringUtils.defaultString(theReq.getParameter(PARAM_RESOURCE)); - RuntimeResourceDefinition def = getContext(theRequest).getResourceDefinition(resourceName); - if (def == null) { - throw new ServletException("Invalid resourceName: " + resourceName); - } - return def; - } - - private ResultType handleClientException(GenericClient theClient, Exception e, ModelMap theModel) { - ResultType returnsResource; - returnsResource = ResultType.NONE; - ourLog.warn("Failed to invoke server", e); - - if (theClient.getLastResponse() == null) { - theModel.put("errorMsg", "Error: " + e.getMessage()); - } - - return returnsResource; - } - private boolean handleSearchParam(String paramIdxString, HttpServletRequest theReq, IQuery theQuery, JsonGenerator theClientCodeJsonWriter) { String nextName = theReq.getParameter("param." + paramIdxString + ".name"); if (isBlank(nextName)) { @@ -1099,349 +836,4 @@ public class Controller { return true; } - private IResource loadAndAddConf(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) { - switch (theRequest.getFhirVersion(myConfig)) { - case DEV: - return loadAndAddConfDstu2(theServletRequest, theRequest, theModel); - case DSTU1: - return loadAndAddConfDstu1(theServletRequest, theRequest, theModel); - case DSTU2: - return loadAndAddConfDstu2(theServletRequest, theRequest, theModel); - } - throw new IllegalStateException("Unknown version: " + theRequest.getFhirVersion(myConfig)); - } - - private Conformance loadAndAddConfDstu1(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) { - CaptureInterceptor interceptor = new CaptureInterceptor(); - GenericClient client = theRequest.newClient(theServletRequest, getContext(theRequest), myConfig, interceptor); - - Conformance conformance; - try { - conformance = (Conformance) client.conformance(); - } catch (Exception e) { - ourLog.warn("Failed to load conformance statement", e); - theModel.put("errorMsg", "Failed to load conformance statement, error was: " + e.toString()); - conformance = new Conformance(); - } - - theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(conformance)); - - Map resourceCounts = new HashMap(); - long total = 0; - for (Rest nextRest : conformance.getRest()) { - for (RestResource nextResource : nextRest.getResource()) { - List exts = nextResource.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); - if (exts != null && exts.size() > 0) { - Number nextCount = ((DecimalDt) (exts.get(0).getValue())).getValueAsNumber(); - resourceCounts.put(nextResource.getType().getValue(), nextCount); - total += nextCount.longValue(); - } - } - } - theModel.put("resourceCounts", resourceCounts); - - if (total > 0) { - for (Rest nextRest : conformance.getRest()) { - Collections.sort(nextRest.getResource(), new Comparator() { - @Override - public int compare(RestResource theO1, RestResource theO2) { - DecimalDt count1 = new DecimalDt(); - List count1exts = theO1.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); - if (count1exts != null && count1exts.size() > 0) { - count1 = (DecimalDt) count1exts.get(0).getValue(); - } - DecimalDt count2 = new DecimalDt(); - List count2exts = theO2.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); - if (count2exts != null && count2exts.size() > 0) { - count2 = (DecimalDt) count2exts.get(0).getValue(); - } - int retVal = count2.compareTo(count1); - if (retVal == 0) { - retVal = theO1.getType().getValue().compareTo(theO2.getType().getValue()); - } - return retVal; - } - }); - } - } - - theModel.put("conf", conformance); - theModel.put("requiredParamExtension", ExtensionConstants.PARAM_IS_REQUIRED); - - return conformance; - } - - private IResource loadAndAddConfDstu2(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) { - CaptureInterceptor interceptor = new CaptureInterceptor(); - GenericClient client = theRequest.newClient(theServletRequest, getContext(theRequest), myConfig, interceptor); - - ca.uhn.fhir.model.dstu2.resource.Conformance conformance; - try { - conformance = (ca.uhn.fhir.model.dstu2.resource.Conformance) client.conformance(); - } catch (Exception e) { - ourLog.warn("Failed to load conformance statement", e); - theModel.put("errorMsg", "Failed to load conformance statement, error was: " + e.toString()); - conformance = new ca.uhn.fhir.model.dstu2.resource.Conformance(); - } - - theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(conformance)); - - Map resourceCounts = new HashMap(); - long total = 0; - for (ca.uhn.fhir.model.dstu2.resource.Conformance.Rest nextRest : conformance.getRest()) { - for (ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource nextResource : nextRest.getResource()) { - List exts = nextResource.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); - if (exts != null && exts.size() > 0) { - Number nextCount = ((DecimalDt) (exts.get(0).getValue())).getValueAsNumber(); - resourceCounts.put(nextResource.getTypeElement().getValue(), nextCount); - total += nextCount.longValue(); - } - } - } - theModel.put("resourceCounts", resourceCounts); - - if (total > 0) { - for (ca.uhn.fhir.model.dstu2.resource.Conformance.Rest nextRest : conformance.getRest()) { - Collections.sort(nextRest.getResource(), new Comparator() { - @Override - public int compare(ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource theO1, ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource theO2) { - DecimalDt count1 = new DecimalDt(); - List count1exts = theO1.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); - if (count1exts != null && count1exts.size() > 0) { - count1 = (DecimalDt) count1exts.get(0).getValue(); - } - DecimalDt count2 = new DecimalDt(); - List count2exts = theO2.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL); - if (count2exts != null && count2exts.size() > 0) { - count2 = (DecimalDt) count2exts.get(0).getValue(); - } - int retVal = count2.compareTo(count1); - if (retVal == 0) { - retVal = theO1.getTypeElement().getValue().compareTo(theO2.getTypeElement().getValue()); - } - return retVal; - } - }); - } - } - - theModel.put("conf", conformance); - theModel.put("requiredParamExtension", ExtensionConstants.PARAM_IS_REQUIRED); - - return conformance; - } - - private String logPrefix(ModelMap theModel) { - return "[server=" + theModel.get("serverId") + "] - "; - } - - private String parseNarrative(HomeRequest theRequest, EncodingEnum theCtEnum, String theResultBody) { - try { - IResource resource = (IResource) theCtEnum.newParser(getContext(theRequest)).parseResource(theResultBody); - String retVal = resource.getText().getDiv().getValueAsString(); - return StringUtils.defaultString(retVal); - } catch (Exception e) { - ourLog.error("Failed to parse resource", e); - return ""; - } - } - - private String preProcessMessageBody(String theBody) { - if (theBody == null) { - return ""; - } - String retVal = theBody.trim(); - - StringBuilder b = new StringBuilder(); - for (int i = 0; i < retVal.length(); i++) { - char nextChar = retVal.charAt(i); - int nextCharI = nextChar; - if (nextCharI == 65533) { - b.append(' '); - continue; - } - if (nextCharI == 160) { - b.append(' '); - continue; - } - if (nextCharI == 194) { - b.append(' '); - continue; - } - b.append(nextChar); - } - retVal = b.toString(); - return retVal; - } - - private void processAndAddLastClientInvocation(GenericClient theClient, ResultType theResultType, ModelMap theModelMap, long theLatency, String outcomeDescription, - CaptureInterceptor theInterceptor, HomeRequest theRequest) { - try { - HttpRequestBase lastRequest = theInterceptor.getLastRequest(); - HttpResponse lastResponse = theInterceptor.getLastResponse(); - String requestBody = null; - String requestUrl = lastRequest != null ? lastRequest.getURI().toASCIIString() : null; - String action = lastRequest != null ? lastRequest.getMethod() : null; - String resultStatus = lastResponse != null ? lastResponse.getStatusLine().toString() : null; - String resultBody = StringUtils.defaultString(theInterceptor.getLastResponseBody()); - - if (lastRequest instanceof HttpEntityEnclosingRequest) { - HttpEntity entity = ((HttpEntityEnclosingRequest) lastRequest).getEntity(); - if (entity.isRepeatable()) { - requestBody = IOUtils.toString(entity.getContent()); - } - } - - ContentType ct = lastResponse != null ? ContentType.get(lastResponse.getEntity()) : null; - String mimeType = ct != null ? ct.getMimeType() : null; - EncodingEnum ctEnum = EncodingEnum.forContentType(mimeType); - String narrativeString = ""; - - StringBuilder resultDescription = new StringBuilder(); - Bundle bundle = null; - - if (ctEnum == null) { - resultDescription.append("Non-FHIR response"); - } else { - switch (ctEnum) { - case JSON: - if (theResultType == ResultType.RESOURCE) { - narrativeString = parseNarrative(theRequest, ctEnum, resultBody); - resultDescription.append("JSON resource"); - } else if (theResultType == ResultType.BUNDLE) { - resultDescription.append("JSON bundle"); - bundle = getContext(theRequest).newJsonParser().parseBundle(resultBody); - } - break; - case XML: - default: - if (theResultType == ResultType.RESOURCE) { - narrativeString = parseNarrative(theRequest, ctEnum, resultBody); - resultDescription.append("XML resource"); - } else if (theResultType == ResultType.BUNDLE) { - resultDescription.append("XML bundle"); - bundle = getContext(theRequest).newXmlParser().parseBundle(resultBody); - } - break; - } - } - - /* - * DSTU2 no longer has a title in the bundle format, but it's still useful here.. - */ - if (bundle != null) { - INarrativeGenerator gen = getContext(theRequest).getNarrativeGenerator(); - if (gen != null) { - for (BundleEntry next : bundle.getEntries()) { - if (next.getTitle().isEmpty() && next.getResource() != null) { - String title = gen.generateTitle(next.getResource()); - next.getTitle().setValue(title); - } - } - } - } - - resultDescription.append(" (").append(resultBody.length() + " bytes)"); - - Header[] requestHeaders = lastRequest != null ? applyHeaderFilters(lastRequest.getAllHeaders()) : new Header[0]; - Header[] responseHeaders = lastResponse != null ? applyHeaderFilters(lastResponse.getAllHeaders()) : new Header[0]; - - theModelMap.put("outcomeDescription", outcomeDescription); - theModelMap.put("resultDescription", resultDescription.toString()); - theModelMap.put("action", action); - theModelMap.put("bundle", bundle); - theModelMap.put("resultStatus", resultStatus); - - theModelMap.put("requestUrl", requestUrl); - theModelMap.put("requestUrlText", formatUrl(theClient.getUrlBase(), requestUrl)); - - String requestBodyText = format(requestBody, ctEnum); - theModelMap.put("requestBody", requestBodyText); - - String resultBodyText = format(resultBody, ctEnum); - theModelMap.put("resultBody", resultBodyText); - - theModelMap.put("resultBodyIsLong", resultBodyText.length() > 1000); - theModelMap.put("requestHeaders", requestHeaders); - theModelMap.put("responseHeaders", responseHeaders); - theModelMap.put("narrative", narrativeString); - theModelMap.put("latencyMs", theLatency); - - } catch (Exception e) { - ourLog.error("Failure during processing", e); - theModelMap.put("errorMsg", "Error during processing: " + e.getMessage()); - } - - } - - public static class CaptureInterceptor implements IClientInterceptor { - - private HttpRequestBase myLastRequest; - private HttpResponse myLastResponse; - private String myResponseBody; - - public HttpRequestBase getLastRequest() { - return myLastRequest; - } - - public HttpResponse getLastResponse() { - return myLastResponse; - } - - public String getLastResponseBody() { - return myResponseBody; - } - - @Override - public void interceptRequest(HttpRequestBase theRequest) { - assert myLastRequest == null; - myLastRequest = theRequest; - } - - @Override - public void interceptResponse(HttpResponse theResponse) throws IOException { - assert myLastResponse == null; - myLastResponse = theResponse; - - HttpEntity respEntity = theResponse.getEntity(); - if (respEntity != null) { - final byte[] bytes; - try { - bytes = IOUtils.toByteArray(respEntity.getContent()); - } catch (IllegalStateException e) { - throw new InternalErrorException(e); - } - - myResponseBody = new String(bytes, "UTF-8"); - theResponse.setEntity(new MyEntityWrapper(respEntity, bytes)); - } - } - - private static class MyEntityWrapper extends HttpEntityWrapper { - - private byte[] myBytes; - - public MyEntityWrapper(HttpEntity theWrappedEntity, byte[] theBytes) { - super(theWrappedEntity); - myBytes = theBytes; - } - - @Override - public InputStream getContent() throws IOException { - return new ByteArrayInputStream(myBytes); - } - - @Override - public void writeTo(OutputStream theOutstream) throws IOException { - theOutstream.write(myBytes); - } - - } - - } - - private enum ResultType { - BUNDLE, NONE, RESOURCE, TAGLIST - } - }