diff --git a/README.md b/README.md index f4ca773..7def907 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ mvn jetty:run Then, browse to the following link to use the server: -[http://localhost:8080/hapi-fhir-jpaserver/](http://localhost:8080/hapi-fhir-jpaserver/) +[http://localhost:8080/](http://localhost:8080/) # Deploying to a Container @@ -40,7 +40,7 @@ This will create a file called `hapi-fhir-jpaserver.war` in your `target` direct Again, browse to the following link to use the server (note that the port 8080 may not be correct depending on how your server is configured). -[http://localhost:8080/hapi-fhir-jpaserver/](http://localhost:8080/hapi-fhir-jpaserver/) +[http://localhost:8080/](http://localhost:8080/) # Customizing The Web Testpage UI @@ -56,10 +56,21 @@ Much of this HAPI starter project can be configured using the properties file in ## MySql -To configure the starter app to use MySQL, instead of the default Derby, update the hapi.properties file to have the following: +To configure the starter app to use MySQL, instead of the default Derby: -* datasource.driver=com.mysql.jdbc.Driver +> Add user and database on your mysql server via mysql cli +``` +CREATE USER 'hapiDbUser'@'localhost' IDENTIFIED BY 'hapiDbPass'; +CREATE DATABASE hapi_dstu3; +GRANT ALL PRIVILEGES ON hapi_dstu3.* to 'hapiDbUser'@'localhost'; +FLUSH PRIVILEGES; +``` + +> Update hapi.properties file to have the following +* datasource.driver=com.mysql.cj.jdbc.Driver * datasource.url=jdbc:mysql://localhost:3306/hapi_dstu3 +* datasource.username=hapiDbUser +* datasource.password=hapiDbPass * hibernate.dialect=org.hibernate.dialect.MySQL5Dialect It is important to use MySQL5Dialect when using MySQL version 5+. diff --git a/pom.xml b/pom.xml index 3bf5549..ca54c6b 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ oss-snapshots - true + false https://oss.sonatype.org/content/repositories/snapshots/ @@ -111,17 +111,6 @@ - - org.ebaysf.web - cors-filter - - - servlet-api - javax.servlet - - - - org.springframework @@ -207,24 +196,20 @@ hapi-fhir-jpaserver - - - - - org.eclipse.jetty - jetty-maven-plugin - 9.4.8.v20180619 - - - /hapi-fhir-jpaserver - true - - - - - - + + + org.eclipse.jetty + jetty-maven-plugin + 9.4.8.v20180619 + + + / + true + + + + org.apache.maven.plugins diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java index e658930..a9957e9 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java @@ -18,7 +18,6 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 { @Autowired private DataSource myDataSource; - /** * We override the paging provider definition so that we can customize * the default/max page sizes for search results. You can set these however diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java index f7d8a77..11ccbcb 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java @@ -43,6 +43,7 @@ public class FhirTesterConfig { .withFhirVersion(HapiProperties.getFhirVersion()) .withBaseUrl(HapiProperties.getServerAddress()) .withName(HapiProperties.getServerName()); + retVal.setRefuseToFetchThirdPartyUrls(HapiProperties.getTesterConfigRefustToFetchThirdPartyUrls()); return retVal; } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java index ca7f255..958c2eb 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java @@ -40,6 +40,9 @@ public class HapiProperties { static final String SUBSCRIPTION_RESTHOOK_ENABLED = "subscription.resthook.enabled"; static final String SUBSCRIPTION_WEBSOCKET_ENABLED = "subscription.websocket.enabled"; static final String TEST_PORT = "test.port"; + static final String TESTER_CONFIG_REFUSE_TO_FETCH_THIRD_PARTY_URLS = "tester.config.refuse_to_fetch_third_party_urls"; + static final String CORS_ENABLED = "cors.enabled"; + static final String CORS_ALLOWED_ORIGIN = "cors.allowed_origin"; static final String ALLOW_CONTAINS_SEARCHES = "allow_contains_searches"; static final String ALLOW_OVERRIDE_DEFAULT_SEARCH_PARAMS = "allow_override_default_search_params"; static final String EMAIL_FROM = "email.from"; @@ -253,6 +256,18 @@ public class HapiProperties { return HapiProperties.getIntegerProperty(TEST_PORT, 0); } + public static Boolean getTesterConfigRefustToFetchThirdPartyUrls() { + return HapiProperties.getBooleanProperty(TESTER_CONFIG_REFUSE_TO_FETCH_THIRD_PARTY_URLS, false); + } + + public static Boolean getCorsEnabled() { + return HapiProperties.getBooleanProperty(CORS_ENABLED, true); + } + + public static String getCorsAllowedOrigin() { + return HapiProperties.getProperty(CORS_ALLOWED_ORIGIN, "*"); + } + public static String getServerBase() { return HapiProperties.getProperty(SERVER_BASE, "/fhir"); } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java index 71bd2d0..c0a3403 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java @@ -4,7 +4,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry; +import ca.uhn.fhir.jpa.model.interceptor.executor.InterceptorService; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; @@ -15,19 +15,22 @@ import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; -import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor; +import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; +import ca.uhn.fhir.jpa.subscription.module.interceptor.SubscriptionDebugLogInterceptor; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Meta; +import org.springframework.web.cors.CorsConfiguration; import org.springframework.context.ApplicationContext; import javax.servlet.ServletException; +import java.util.Arrays; import java.util.List; public class JpaRestfulServer extends RestfulServer { @@ -45,10 +48,6 @@ public class JpaRestfulServer extends RestfulServer { */ ApplicationContext appCtx = (ApplicationContext) getServletContext().getAttribute("org.springframework.web.context.WebApplicationContext.ROOT"); -// if (HapiProperties.getSubscriptionWebsocketEnabled()) { -// appCtx.register(WebsocketDispatcherConfig.class); -// } - /* * ResourceProviders are fetched from the Spring context */ @@ -170,22 +169,43 @@ public class JpaRestfulServer extends RestfulServer { registerProvider(retriggeringProvider); } + // Define your CORS configuration. This is an example + // showing a typical setup. You should customize this + // to your specific needs + if(HapiProperties.getCorsEnabled()) { + CorsConfiguration config = new CorsConfiguration(); + config.addAllowedHeader("x-fhir-starter"); + config.addAllowedHeader("Origin"); + config.addAllowedHeader("Accept"); + config.addAllowedHeader("X-Requested-With"); + config.addAllowedHeader("Content-Type"); + + config.addAllowedOrigin(HapiProperties.getCorsAllowedOrigin()); + + config.addExposedHeader("Location"); + config.addExposedHeader("Content-Location"); + config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); + + // Create the interceptor and register it + CorsInterceptor interceptor = new CorsInterceptor(config); + registerInterceptor(interceptor); + } + // If subscriptions are enabled, we want to register the interceptor that // will activate them and match results against them if (HapiProperties.getSubscriptionWebsocketEnabled() || HapiProperties.getSubscriptionEmailEnabled() || HapiProperties.getSubscriptionRestHookEnabled()) { - IInterceptorRegistry interceptorRegistry = appCtx.getBean(IInterceptorRegistry.class); + // Loads subscription interceptors (SubscriptionActivatingInterceptor, SubscriptionMatcherInterceptor) + // with activation of scheduled subscription + SubscriptionInterceptorLoader subscriptionInterceptorLoader = appCtx.getBean(SubscriptionInterceptorLoader.class); + subscriptionInterceptorLoader.registerInterceptors(); - SubscriptionActivatingInterceptor subscriptionActivatingInterceptor = appCtx.getBean(SubscriptionActivatingInterceptor.class); - interceptorRegistry.registerInterceptor(subscriptionActivatingInterceptor); - - SubscriptionMatcherInterceptor subscriptionMatcherInterceptor = appCtx.getBean(SubscriptionMatcherInterceptor.class); - subscriptionMatcherInterceptor.start(); - interceptorRegistry.registerInterceptor(subscriptionMatcherInterceptor); + // Subscription debug logging + InterceptorService interceptorService = (InterceptorService) appCtx.getBean("interceptorService"); + interceptorService.registerInterceptor(new SubscriptionDebugLogInterceptor()); } - } } diff --git a/src/main/resources/hapi.properties b/src/main/resources/hapi.properties index b30faad..0d7e11e 100644 --- a/src/main/resources/hapi.properties +++ b/src/main/resources/hapi.properties @@ -6,18 +6,12 @@ fhir_version=DSTU3 # This is the address that the FHIR server will report as its own address. # If this server will be deployed (for example) to an internet accessible # server, put the DNS name of that server here. -server_address=http://localhost/fhir/ - -# For Jetty, use this: -# server_address=http://localhost:8080/hapi-fhir-jpaserver/fhir/ +server_address=http://localhost:8080/fhir/ # This is the context path for the FHIR endpoint. If this is changed, the # setting above should also be changed. server.base=/fhir -# For Jetty, use this: -# server.base=/hapi-fhir-jpaserver/fhir - default_encoding=JSON etag_support=ENABLED default_page_size=20 @@ -53,6 +47,9 @@ hibernate.cache.use_minimal_puts=false hibernate.search.default.directory_provider=filesystem hibernate.search.default.indexBase=target/lucenefiles hibernate.search.lucene_version=LUCENE_CURRENT +tester.config.refuse_to_fetch_third_party_urls=false +cors.enabled=true +cors.allowed_origin=* ################################################## # Subscriptions diff --git a/src/main/webapp/WEB-INF/templates/tmpl-head.html b/src/main/webapp/WEB-INF/templates/tmpl-head.html new file mode 100644 index 0000000..3d7bb4e --- /dev/null +++ b/src/main/webapp/WEB-INF/templates/tmpl-head.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index b49f111..6ce88b0 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -31,74 +31,24 @@ contextConfigLocation - ca.uhn.fhir.jpa.starter.FhirTesterConfig, + ca.uhn.fhir.jpa.starter.FhirTesterConfig 2 + + spring + / + fhirServlet ca.uhn.fhir.jpa.starter.JpaRestfulServer 1 - fhirServlet /fhir/* - - spring - / - - - - - - - CORS Filter - org.ebaysf.web.cors.CORSFilter - - A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials. - cors.allowed.origins - * - - - A comma separated list of HTTP verbs, using which a CORS request can be made. - cors.allowed.methods - GET,POST,PUT,DELETE,OPTIONS - - - A comma separated list of allowed headers when making a non simple CORS request. - cors.allowed.headers - Accept,Access-Control-Request-Headers,Access-Control-Request-Method,Cache-Control,Content-Type,Origin,Prefer,X-FHIR-Starter,X-Requested-With - - - A comma separated list non-standard response headers that will be exposed to XHR2 object. - cors.exposed.headers - Location,Content-Location - - - A flag that suggests if CORS is supported with cookies - cors.support.credentials - true - - - A flag to control logging - cors.logging.enabled - true - - - Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache. - cors.preflight.maxage - 300 - - - - CORS Filter - /* - - - diff --git a/src/main/webapp/img/favicon.ico b/src/main/webapp/img/favicon.ico new file mode 100644 index 0000000..4c2419d Binary files /dev/null and b/src/main/webapp/img/favicon.ico differ diff --git a/src/main/webapp/js/moment-with-locales.min.js b/src/main/webapp/js/moment-with-locales.min.js new file mode 100644 index 0000000..d81e02c --- /dev/null +++ b/src/main/webapp/js/moment-with-locales.min.js @@ -0,0 +1 @@ +!function(e,a){"object"==typeof exports&&"undefined"!=typeof module?module.exports=a():"function"==typeof define&&define.amd?define(a):e.moment=a()}(this,function(){"use strict";var e,n;function l(){return e.apply(null,arguments)}function _(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function i(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function o(e){return void 0===e}function m(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function u(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function M(e,a){var t,s=[];for(t=0;t>>0,s=0;sTe(e)?(d=e+1,_-Te(e)):(d=e,_),{year:d,dayOfYear:r}}function Ce(e,a,t){var s,n,d=Ne(e.year(),a,t),r=Math.floor((e.dayOfYear()-d-1)/7)+1;return r<1?s=r+Ie(n=e.year()-1,a,t):r>Ie(e.year(),a,t)?(s=r-Ie(e.year(),a,t),n=e.year()+1):(n=e.year(),s=r),{week:s,year:n}}function Ie(e,a,t){var s=Ne(e,a,t),n=Ne(e+1,a,t);return(Te(e)-s+n)/7}C("w",["ww",2],"wo","week"),C("W",["WW",2],"Wo","isoWeek"),O("week","w"),O("isoWeek","W"),E("week",5),E("isoWeek",5),ie("w",B),ie("ww",B,V),ie("W",B),ie("WW",B,V),Me(["w","ww","W","WW"],function(e,a,t,s){a[s.substr(0,1)]=g(e)});function Ue(e,a){return e.slice(a,7).concat(e.slice(0,a))}C("d",0,"do","day"),C("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),C("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),C("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),C("e",0,0,"weekday"),C("E",0,0,"isoWeekday"),O("day","d"),O("weekday","e"),O("isoWeekday","E"),E("day",11),E("weekday",11),E("isoWeekday",11),ie("d",B),ie("e",B),ie("E",B),ie("dd",function(e,a){return a.weekdaysMinRegex(e)}),ie("ddd",function(e,a){return a.weekdaysShortRegex(e)}),ie("dddd",function(e,a){return a.weekdaysRegex(e)}),Me(["dd","ddd","dddd"],function(e,a,t,s){var n=t._locale.weekdaysParse(e,s,t._strict);null!=n?a.d=n:Y(t).invalidWeekday=e}),Me(["d","e","E"],function(e,a,t,s){a[s]=g(e)});var Ge="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var Ve="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var Ke="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var Ze=re;var $e=re;var Be=re;function qe(){function e(e,a){return a.length-e.length}var a,t,s,n,d,r=[],_=[],i=[],o=[];for(a=0;a<7;a++)t=c([2e3,1]).day(a),s=this.weekdaysMin(t,""),n=this.weekdaysShort(t,""),d=this.weekdays(t,""),r.push(s),_.push(n),i.push(d),o.push(s),o.push(n),o.push(d);for(r.sort(e),_.sort(e),i.sort(e),o.sort(e),a=0;a<7;a++)_[a]=me(_[a]),i[a]=me(i[a]),o[a]=me(o[a]);this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+_.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+r.join("|")+")","i")}function Qe(){return this.hours()%12||12}function Xe(e,a){C(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),a)})}function ea(e,a){return a._meridiemParse}C("H",["HH",2],0,"hour"),C("h",["hh",2],0,Qe),C("k",["kk",2],0,function(){return this.hours()||24}),C("hmm",0,0,function(){return""+Qe.apply(this)+F(this.minutes(),2)}),C("hmmss",0,0,function(){return""+Qe.apply(this)+F(this.minutes(),2)+F(this.seconds(),2)}),C("Hmm",0,0,function(){return""+this.hours()+F(this.minutes(),2)}),C("Hmmss",0,0,function(){return""+this.hours()+F(this.minutes(),2)+F(this.seconds(),2)}),Xe("a",!0),Xe("A",!1),O("hour","h"),E("hour",13),ie("a",ea),ie("A",ea),ie("H",B),ie("h",B),ie("k",B),ie("HH",B,V),ie("hh",B,V),ie("kk",B,V),ie("hmm",q),ie("hmmss",Q),ie("Hmm",q),ie("Hmmss",Q),le(["H","HH"],Ye),le(["k","kk"],function(e,a,t){var s=g(e);a[Ye]=24===s?0:s}),le(["a","A"],function(e,a,t){t._isPm=t._locale.isPM(e),t._meridiem=e}),le(["h","hh"],function(e,a,t){a[Ye]=g(e),Y(t).bigHour=!0}),le("hmm",function(e,a,t){var s=e.length-2;a[Ye]=g(e.substr(0,s)),a[ye]=g(e.substr(s)),Y(t).bigHour=!0}),le("hmmss",function(e,a,t){var s=e.length-4,n=e.length-2;a[Ye]=g(e.substr(0,s)),a[ye]=g(e.substr(s,2)),a[fe]=g(e.substr(n)),Y(t).bigHour=!0}),le("Hmm",function(e,a,t){var s=e.length-2;a[Ye]=g(e.substr(0,s)),a[ye]=g(e.substr(s))}),le("Hmmss",function(e,a,t){var s=e.length-4,n=e.length-2;a[Ye]=g(e.substr(0,s)),a[ye]=g(e.substr(s,2)),a[fe]=g(e.substr(n))});var aa,ta=Se("Hours",!0),sa={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Oe,monthsShort:Pe,week:{dow:0,doy:6},weekdays:Ge,weekdaysMin:Ke,weekdaysShort:Ve,meridiemParse:/[ap]\.?m?\.?/i},na={},da={};function ra(e){return e?e.toLowerCase().replace("_","-"):e}function _a(e){var a=null;if(!na[e]&&"undefined"!=typeof module&&module&&module.exports)try{a=aa._abbr,require("./locale/"+e),ia(a)}catch(e){}return na[e]}function ia(e,a){var t;return e&&((t=o(a)?ma(e):oa(e,a))?aa=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),aa._abbr}function oa(e,a){if(null===a)return delete na[e],null;var t,s=sa;if(a.abbr=e,null!=na[e])S("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=na[e]._config;else if(null!=a.parentLocale)if(null!=na[a.parentLocale])s=na[a.parentLocale]._config;else{if(null==(t=_a(a.parentLocale)))return da[a.parentLocale]||(da[a.parentLocale]=[]),da[a.parentLocale].push({name:e,config:a}),null;s=t._config}return na[e]=new j(b(s,a)),da[e]&&da[e].forEach(function(e){oa(e.name,e.config)}),ia(e),na[e]}function ma(e){var a;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return aa;if(!_(e)){if(a=_a(e))return a;e=[e]}return function(e){for(var a,t,s,n,d=0;d=a&&r(n,t,!0)>=a-1)break;a--}d++}return aa}(e)}function ua(e){var a,t=e._a;return t&&-2===Y(e).overflow&&(a=t[Le]<0||11je(t[he],t[Le])?ce:t[Ye]<0||24Ie(t,d,r)?Y(e)._overflowWeeks=!0:null!=i?Y(e)._overflowWeekday=!0:(_=Re(t,s,n,d,r),e._a[he]=_.year,e._dayOfYear=_.dayOfYear)}(e),null!=e._dayOfYear&&(d=la(e._a[he],s[he]),(e._dayOfYear>Te(d)||0===e._dayOfYear)&&(Y(e)._overflowDayOfYear=!0),t=Je(d,0,e._dayOfYear),e._a[Le]=t.getUTCMonth(),e._a[ce]=t.getUTCDate()),a=0;a<3&&null==e._a[a];++a)e._a[a]=r[a]=s[a];for(;a<7;a++)e._a[a]=r[a]=null==e._a[a]?2===a?1:0:e._a[a];24===e._a[Ye]&&0===e._a[ye]&&0===e._a[fe]&&0===e._a[ke]&&(e._nextDay=!0,e._a[Ye]=0),e._d=(e._useUTC?Je:function(e,a,t,s,n,d,r){var _;return e<100&&0<=e?(_=new Date(e+400,a,t,s,n,d,r),isFinite(_.getFullYear())&&_.setFullYear(e)):_=new Date(e,a,t,s,n,d,r),_}).apply(null,r),n=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[Ye]=24),e._w&&void 0!==e._w.d&&e._w.d!==n&&(Y(e).weekdayMismatch=!0)}}var ha=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,La=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ca=/Z|[+-]\d\d(?::?\d\d)?/,Ya=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],ya=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],fa=/^\/?Date\((\-?\d+)/i;function ka(e){var a,t,s,n,d,r,_=e._i,i=ha.exec(_)||La.exec(_);if(i){for(Y(e).iso=!0,a=0,t=Ya.length;at.valueOf():t.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},Mt.isLocal=function(){return!!this.isValid()&&!this._isUTC},Mt.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},Mt.isUtc=Ra,Mt.isUTC=Ra,Mt.zoneAbbr=function(){return this._isUTC?"UTC":""},Mt.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},Mt.dates=t("dates accessor is deprecated. Use date instead.",_t),Mt.months=t("months accessor is deprecated. Use month instead",Ae),Mt.years=t("years accessor is deprecated. Use year instead",ve),Mt.zone=t("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,a){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,a),this):-this.utcOffset()}),Mt.isDSTShifted=t("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e={};if(k(e,this),(e=va(e))._a){var a=e._isUTC?c(e._a):Ha(e._a);this._isDSTShifted=this.isValid()&&0 ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size()); + + /* + * Attach websocket + */ + + WebSocketClient myWebSocketClient = new WebSocketClient(); + SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); + + myWebSocketClient.start(); + URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket"); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + ourLog.info("Connecting to : {}", echoUri); + Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); + Session session = connection.get(2, TimeUnit.SECONDS); + + ourLog.info("Connected to WS: {}", session.isOpen()); + + /* + * Create a matching resource + */ + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + ourClient.create().resource(obs).execute(); + + // Give some time for the subscription to deliver + Thread.sleep(2000); + + /* + * Ensure that we receive a ping on the websocket + */ + waitForSize(1, () -> mySocketImplementation.myPingCount); + + /* + * Clean up + */ + ourClient.delete().resourceById(mySubscriptionId).execute(); + } + @AfterClass public static void afterClass() throws Exception { ourServer.stop(); @@ -56,13 +128,7 @@ public class ExampleServerDstu3IT { @BeforeClass public static void beforeClass() throws Exception { - /* - * This runs under maven, and I'm not sure how else to figure out the target directory from code.. - */ - String path = ExampleServerDstu3IT.class.getClassLoader().getResource(".keep_hapi-fhir-jpaserver-starter").getPath(); - path = new File(path).getParent(); - path = new File(path).getParent(); - path = new File(path).getParent(); + String path = Paths.get("").toAbsolutePath().toString(); ourLog.info("Project base path is: {}", path); diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java index 6858b41..ce77acd 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java @@ -22,9 +22,9 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import java.io.File; import java.io.IOException; import java.net.URI; +import java.nio.file.Paths; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -64,7 +64,6 @@ public class ExampleServerR4IT { assertEquals(methodName, pt2.getName().get(0).getFamily()); } - @Test public void testWebsocketSubscription() throws Exception { /* @@ -123,7 +122,6 @@ public class ExampleServerR4IT { ourClient.delete().resourceById(mySubscriptionId).execute(); } - @AfterClass public static void afterClass() throws Exception { ourServer.stop(); @@ -131,13 +129,7 @@ public class ExampleServerR4IT { @BeforeClass public static void beforeClass() throws Exception { - /* - * This runs under maven, and I'm not sure how else to figure out the target directory from code.. - */ - String path = ExampleServerR4IT.class.getClassLoader().getResource(".keep_hapi-fhir-jpaserver-starter").getPath(); - path = new File(path).getParent(); - path = new File(path).getParent(); - path = new File(path).getParent(); + String path = Paths.get("").toAbsolutePath().toString(); ourLog.info("Project base path is: {}", path); diff --git a/src/test/resources/.keep_hapi-fhir-jpaserver-starter b/src/test/resources/.keep_hapi-fhir-jpaserver-starter deleted file mode 100644 index e69de29..0000000