From c9ce995eda8928a7fbf3f391b82e4b212ac5d35c Mon Sep 17 00:00:00 2001 From: Volker Schmidt Date: Wed, 7 Nov 2018 18:13:18 +0100 Subject: [PATCH] Implemented setup wizard. --- .gitattributes | 2 + app/pom.xml | 10 + .../org/dhis2/fhir/adapter/DemoClient.java | 8 +- .../dhis2/fhir/adapter/WebSecurityConfig.java | 2 + .../adapter/setup/FhirDhisCodeMapping.java | 64 +++++ .../org/dhis2/fhir/adapter/setup/HttpUrl.java | 53 ++++ .../fhir/adapter/setup/HttpUrlValidator.java | 70 +++++ .../adapter/setup/OrganizationCodeSetup.java | 142 ++++++++++ .../setup/RemoteSubscriptionAdapterSetup.java | 73 ++++++ .../setup/RemoteSubscriptionDhisSetup.java | 73 ++++++ .../setup/RemoteSubscriptionFhirSetup.java | 116 ++++++++ .../setup/RemoteSubscriptionSetup.java | 94 +++++++ .../org/dhis2/fhir/adapter/setup/Setup.java | 68 +++++ .../fhir/adapter/setup/SetupController.java | 110 ++++++++ .../fhir/adapter/setup/SetupException.java | 45 ++++ .../fhir/adapter/setup/SetupService.java | 247 ++++++++++++++++++ .../fhir/adapter/setup/SystemUriSetup.java | 105 ++++++++ .../org/dhis2/fhir/adapter/setup/Uri.java | 53 ++++ .../fhir/adapter/setup/UriValidator.java | 63 +++++ app/src/main/resources/application.yml | 2 +- .../main/resources/default-application.yml | 2 + app/src/main/resources/static/favicon.ico | Bin 0 -> 1150 bytes app/src/main/resources/templates/setup.html | 175 +++++++++++++ .../cache/AbstractSimpleCacheConfig.java | 19 +- .../adapter/cache/SimpleRedisCacheConfig.java | 28 +- .../dhis2/fhir/adapter/lock/LockContext.java | 4 +- .../fhir/adapter/lock/LockException.java | 2 +- .../adapter/lock/impl/LockContextImpl.java | 2 +- .../dhis2/fhir/adapter/queue/QueueConfig.java | 3 + .../FatalScriptCompilationException.java | 51 ++++ .../script/ScriptCompilationException.java | 44 ++++ .../fhir/adapter/script/ScriptCompiler.java | 51 ++++ .../script/impl/ScriptCompilerImpl.java | 92 +++++++ .../fhir/adapter/dhis/config/DhisConfig.java | 9 +- .../dhis/config/DhisEndpointConfig.java | 37 ++- .../dhis/config/DhisMetadataCacheConfig.java | 4 + .../DhisWebApiAuthenticationProvider.java | 3 +- .../adapter/dhis/security/SecurityConfig.java | 2 +- .../Dstu3CodeFhirToDhisTransformerUtils.java | 4 +- fhir/pom.xml | 1 + .../fhir/metadata/model/AbstractRule.java | 12 +- .../adapter/fhir/metadata/model/Code.java | 15 ++ .../adapter/fhir/metadata/model/System.java | 4 +- .../fhir/metadata/model/SystemCode.java | 22 +- .../metadata/model/VersionedBaseMetadata.java | 18 ++ .../metadata/repository/CodeRepository.java | 10 + .../RemoteSubscriptionRepository.java | 11 + .../RuleFindAllByInputDataKeyGenerator.java | 4 +- .../SystemCodeFindAllByCodesKeyGenerator.java | 4 +- .../impl/AdapterMetadataCacheConfig.java | 4 + ...ustomRemoteSubscriptionRepositoryImpl.java | 6 +- .../impl/CustomRuleRepositoryImpl.java | 25 +- .../FhirAdapterMetadataEventListener.java | 37 +-- .../VersionedBaseMetadataEventListener.java | 78 ------ .../BeforeCreateSaveCodeValidator.java | 12 + ...CreateSaveRemoteSubscriptionValidator.java | 15 +- ...BeforeCreateSaveScriptSourceValidator.java | 20 ++ .../BeforeCreateSaveSystemValidator.java | 8 + .../repository/validator/ValidatorUtils.java | 9 +- .../adapter/fhir/model/SystemCodeValue.java | 4 +- .../fhir/remote/impl/RemoteConfig.java | 4 + .../remote/impl/RemoteProcessorConfig.java | 2 + .../repository/impl/FhirRepositoryImpl.java | 2 +- .../impl/FhirResourceCacheConfig.java | 4 + .../repository/impl/RepositoryConfig.java | 5 + .../fhir/security/AdapterSecurityConfig.java | 4 +- .../fhir/security/AdapterSecurityUtils.java | 69 +++++ .../transform/impl/TransformationConfig.java | 14 + ...bstractCodeFhirToDhisTransformerUtils.java | 43 ++- .../AbstractFhirClientTransformUtils.java | 4 +- ...tIdentifierFhirToDhisTransformerUtils.java | 4 +- .../OrganizationUnitTransformerUtils.java | 106 ++++++++ .../fhir/transform/model/ResourceSystem.java | 12 +- .../production/V1.0.0.0_0_1__Initial.sql | 168 ++++++++---- .../V1.0.0.0_0_2__Initial_Sample_Data.sql | 2 +- 75 files changed, 2460 insertions(+), 234 deletions(-) create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/FhirDhisCodeMapping.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrl.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrlValidator.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/OrganizationCodeSetup.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionAdapterSetup.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionDhisSetup.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionFhirSetup.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionSetup.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/Setup.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/SetupController.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/SetupException.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/SetupService.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/SystemUriSetup.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/Uri.java create mode 100644 app/src/main/java/org/dhis2/fhir/adapter/setup/UriValidator.java create mode 100644 app/src/main/resources/static/favicon.ico create mode 100644 app/src/main/resources/templates/setup.html create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/script/FatalScriptCompilationException.java create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/script/ScriptCompilationException.java create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/script/ScriptCompiler.java create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/script/impl/ScriptCompilerImpl.java rename fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/{key => cache}/RuleFindAllByInputDataKeyGenerator.java (95%) rename fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/{key => cache}/SystemCodeFindAllByCodesKeyGenerator.java (94%) delete mode 100644 fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/VersionedBaseMetadataEventListener.java create mode 100644 fhir/src/main/java/org/dhis2/fhir/adapter/fhir/security/AdapterSecurityUtils.java create mode 100644 fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/OrganizationUnitTransformerUtils.java diff --git a/.gitattributes b/.gitattributes index 89832b61..5dbf0ba0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,6 +6,7 @@ LICENSE text *.MF text *.csv text +*.html text *.java text *.json text *.md text @@ -20,6 +21,7 @@ LICENSE text # Denote all files that are truly binary and should not be modified. *.pdf binary +*.ico binary # Denote all files that must use UNIX style line endings. *.sh text eol=lf diff --git a/app/pom.xml b/app/pom.xml index 5f60f0e3..abea6e17 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -51,6 +51,16 @@ + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-devtools + true + + org.dhis2.fhir.adapter dhis2-fhir-adapter-fhir diff --git a/app/src/main/java/org/dhis2/fhir/adapter/DemoClient.java b/app/src/main/java/org/dhis2/fhir/adapter/DemoClient.java index 20973883..b4c879c2 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/DemoClient.java +++ b/app/src/main/java/org/dhis2/fhir/adapter/DemoClient.java @@ -117,7 +117,7 @@ public static void main( String[] args ) Patient child = new Patient(); child.setIdElement( IdType.newRandomUuid() ); child.addIdentifier() - .setSystem( "http://example.sl/national-patient-id" ) + .setSystem( "http://example.sl/patients" ) .setValue( childNationalId ); child.addName() .setFamily( "West" ) @@ -147,7 +147,7 @@ public static void main( String[] args ) Patient mother = new Patient(); mother.setIdElement( IdType.newRandomUuid() ); mother.addIdentifier() - .setSystem( "http://example.sl/national-patient-id" ) + .setSystem( "http://example.sl/patients" ) .setValue( motherNationalId ); mother.addName() .setFamily( "West" ) @@ -358,13 +358,13 @@ public static void main( String[] args ) .setFullUrl( child.getId() ) .getRequest() .setMethod( Bundle.HTTPVerb.PUT ) - .setUrl( "Patient?identifier=http://example.sl/national-patient-id|" + childNationalId ); + .setUrl( "Patient?identifier=http://example.sl/patients|" + childNationalId ); bundle.addEntry() .setResource( mother ) .setFullUrl( mother.getId() ) .getRequest() .setMethod( Bundle.HTTPVerb.PUT ) - .setUrl( "Patient?identifier=http://example.sl/national-patient-id|" + motherNationalId ); + .setUrl( "Patient?identifier=http://example.sl/patients|" + motherNationalId ); bundle.addEntry() .setResource( location ) .setFullUrl( location.getId() ) diff --git a/app/src/main/java/org/dhis2/fhir/adapter/WebSecurityConfig.java b/app/src/main/java/org/dhis2/fhir/adapter/WebSecurityConfig.java index 2f4827a0..b84b217b 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/WebSecurityConfig.java +++ b/app/src/main/java/org/dhis2/fhir/adapter/WebSecurityConfig.java @@ -33,6 +33,7 @@ import org.dhis2.fhir.adapter.dhis.security.SecurityConfig; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -75,6 +76,7 @@ protected void configure( @Nonnull HttpSecurity http ) throws Exception http .authorizeRequests() .antMatchers( "/remote-fhir-rest-hook/**" ).permitAll() + .antMatchers( HttpMethod.GET, "/setup" ).permitAll() .anyRequest().authenticated() .and() .httpBasic().realmName( DHIS_BASIC_REALM ); diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/FhirDhisCodeMapping.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/FhirDhisCodeMapping.java new file mode 100644 index 00000000..96a70b6a --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/FhirDhisCodeMapping.java @@ -0,0 +1,64 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javax.annotation.Nonnull; +import java.io.Serializable; + +/** + * The mapping between FHIR and DHIS2 code. + * + * @author volsch + */ +public class FhirDhisCodeMapping implements Serializable +{ + private static final long serialVersionUID = 1954015049643872444L; + + private final String fhirCode; + + private final String dhisCode; + + public FhirDhisCodeMapping( @Nonnull String fhirCode, @Nonnull String dhisCode ) + { + this.fhirCode = fhirCode; + this.dhisCode = dhisCode; + } + + @Nonnull + public String getFhirCode() + { + return fhirCode; + } + + @Nonnull + public String getDhisCode() + { + return dhisCode; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrl.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrl.java new file mode 100644 index 00000000..dd62507b --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrl.java @@ -0,0 +1,53 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Validation that a URL is a valid HTTP/HTTPS URL. + * + * @author volsch + */ +@Constraint( validatedBy = HttpUrlValidator.class ) +@Target( { ElementType.FIELD } ) +@Retention( RetentionPolicy.RUNTIME ) +public @interface HttpUrl +{ + String message() default "Not a valid HTTP/HTTPS URL."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} \ No newline at end of file diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrlValidator.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrlValidator.java new file mode 100644 index 00000000..96bf4f80 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrlValidator.java @@ -0,0 +1,70 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Validator that checks that a URL is a valid HTTP/HTTPS URL. + * + * @author volsch + */ +public class HttpUrlValidator implements ConstraintValidator +{ + @Override + public boolean isValid( String value, ConstraintValidatorContext context ) + { + if ( value == null ) + { + return true; + } + + final String protocol; + try + { + protocol = new URL( value ).getProtocol(); + } + catch ( MalformedURLException e ) + { + context.buildConstraintViolationWithTemplate( "Not a valid URL." ); + return false; + } + + if ( !"http".equals( protocol ) && !"https".equals( protocol ) ) + { + context.buildConstraintViolationWithTemplate( "URL must use protocol HTTP or HTTPS." ); + return false; + } + + return true; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/OrganizationCodeSetup.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/OrganizationCodeSetup.java new file mode 100644 index 00000000..e130bd81 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/OrganizationCodeSetup.java @@ -0,0 +1,142 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.dhis2.fhir.adapter.fhir.metadata.model.Code; +import org.dhis2.fhir.adapter.fhir.metadata.model.SystemCode; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.Serializable; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Setup of organization code mapping. + * + * @author volsch + */ +public class OrganizationCodeSetup implements Serializable +{ + private static final long serialVersionUID = -2918523175013256204L; + + public static final String DEFAULT_CODE_PREFIX = "OU_"; + + private String mappings; + + private boolean fallback = true; + + @Size( max = Code.MAX_CODE_LENGTH, message = "Must not be longer than {max} characters." ) + private String defaultDhisCode; + + public String getMappings() + { + return mappings; + } + + public void setMappings( String mappings ) + { + this.mappings = mappings; + } + + public boolean isFallback() + { + return fallback; + } + + public void setFallback( boolean fallback ) + { + this.fallback = fallback; + } + + public String getDefaultDhisCode() + { + return defaultDhisCode; + } + + public void setDefaultDhisCode( String defaultDhisCode ) + { + this.defaultDhisCode = defaultDhisCode; + } + + @NotNull( message = "The syntax of the input is incorrect." ) + public List getCodeMappings() + { + if ( getMappings() == null ) + { + return Collections.emptyList(); + } + final List codeMappings = new ArrayList<>(); + try + { + final Set fhirCodes = new HashSet<>(); + final LineNumberReader reader = new LineNumberReader( new StringReader( getMappings() ) ); + String line; + while ( (line = reader.readLine()) != null ) + { + line = line.trim(); + if ( !line.isEmpty() ) + { + final String[] values = line.split( "\\s*[,;:|]\\s*|\\s+" ); + if ( values.length != 2 ) + { + return null; + } + final FhirDhisCodeMapping codeMapping = new FhirDhisCodeMapping( values[0], values[1] ); + // FHIR codes must be unique + if ( !fhirCodes.add( codeMapping.getFhirCode() ) ) + { + return null; + } + if ( codeMapping.getFhirCode().length() > SystemCode.MAX_SYSTEM_CODE_LENGTH ) + { + return null; + } + if ( codeMapping.getDhisCode().length() + DEFAULT_CODE_PREFIX.length() > Code.MAX_MAPPED_CODE_LENGTH ) + { + return null; + } + codeMappings.add( codeMapping ); + } + } + } + catch ( IOException e ) + { + // must not happen when reading from an internal stream + throw new IllegalStateException( e ); + } + return codeMappings; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionAdapterSetup.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionAdapterSetup.java new file mode 100644 index 00000000..a8bc4ba1 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionAdapterSetup.java @@ -0,0 +1,73 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.dhis2.fhir.adapter.fhir.metadata.model.SubscriptionAdapterEndpoint; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.io.Serializable; + +/** + * The setup of the remote subscription for the Adapter. + * + * @author volsch + */ +public class RemoteSubscriptionAdapterSetup implements Serializable +{ + private static final long serialVersionUID = 7653552278753840057L; + + @NotBlank( message = "Adapter base URL must not be blank." ) + @HttpUrl + private String baseUrl; + + @NotBlank + @Size( max = SubscriptionAdapterEndpoint.MAX_AUTHORIZATION_HEADER_LENGTH, message = "Authorization header value must not be longer than {max} characters." ) + private String authorizationHeaderValue; + + public String getBaseUrl() + { + return baseUrl; + } + + public void setBaseUrl( String baseUrl ) + { + this.baseUrl = baseUrl; + } + + public String getAuthorizationHeaderValue() + { + return authorizationHeaderValue; + } + + public void setAuthorizationHeaderValue( String authorizationHeaderValue ) + { + this.authorizationHeaderValue = authorizationHeaderValue; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionDhisSetup.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionDhisSetup.java new file mode 100644 index 00000000..7558285a --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionDhisSetup.java @@ -0,0 +1,73 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.dhis2.fhir.adapter.fhir.metadata.model.SubscriptionDhisEndpoint; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.io.Serializable; + +/** + * The setup of the remote subscription for DHIS2. + * + * @author volsch + */ +public class RemoteSubscriptionDhisSetup implements Serializable +{ + private static final long serialVersionUID = 7653552278753840057L; + + @NotBlank( message = "Username must not be blank." ) + @Size( max = SubscriptionDhisEndpoint.MAX_USERNAME_LENGTH, message = "Username must not be longer than {max} characters." ) + private String username; + + @NotBlank( message = "Password must not be blank." ) + @Size( max = SubscriptionDhisEndpoint.MAX_PASSWORD_LENGTH, message = "Password must not be longer than {max} characters." ) + private String password; + + public String getUsername() + { + return username; + } + + public void setUsername( String username ) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword( String password ) + { + this.password = password; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionFhirSetup.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionFhirSetup.java new file mode 100644 index 00000000..1a4faa74 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionFhirSetup.java @@ -0,0 +1,116 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.dhis2.fhir.adapter.fhir.metadata.model.RequestHeader; +import org.dhis2.fhir.adapter.fhir.metadata.model.SubscriptionFhirEndpoint; +import org.dhis2.fhir.adapter.fhir.metadata.model.SubscriptionType; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.io.Serializable; + +/** + * The setup of the remote subscription for FHIR. + * + * @author volsch + */ +public class RemoteSubscriptionFhirSetup implements Serializable +{ + private static final long serialVersionUID = -4466440362652976141L; + + @NotBlank( message = "URL must not be blank." ) + @HttpUrl + @Size( max = SubscriptionFhirEndpoint.MAX_BASE_URL_LENGTH, message = "URL must note be longer than {max} characters." ) + private String baseUrl; + + @Size( max = RequestHeader.MAX_NAME_LENGTH, message = "Header name must not be longer than {max} characters." ) + private String headerName; + + @Size( max = RequestHeader.MAX_NAME_LENGTH, message = "Header value must not be longer than {max} characters." ) + private String headerValue; + + @NotNull( message = "Subscription type must be selected." ) + private SubscriptionType subscriptionType = SubscriptionType.REST_HOOK_WITH_JSON_PAYLOAD; + + @Min( value = 0, message = "Must be a positive value." ) + private int toleranceMillis = 5_000; + + public String getBaseUrl() + { + return baseUrl; + } + + public void setBaseUrl( String baseUrl ) + { + this.baseUrl = baseUrl; + } + + public String getHeaderName() + { + return headerName; + } + + public void setHeaderName( String headerName ) + { + this.headerName = headerName; + } + + public String getHeaderValue() + { + return headerValue; + } + + public void setHeaderValue( String headerValue ) + { + this.headerValue = headerValue; + } + + public SubscriptionType getSubscriptionType() + { + return subscriptionType; + } + + public void setSubscriptionType( SubscriptionType subscriptionType ) + { + this.subscriptionType = subscriptionType; + } + + public int getToleranceMillis() + { + return toleranceMillis; + } + + public void setToleranceMillis( int toleranceMillis ) + { + this.toleranceMillis = toleranceMillis; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionSetup.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionSetup.java new file mode 100644 index 00000000..2b4dbab8 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionSetup.java @@ -0,0 +1,94 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javax.validation.Valid; +import java.io.Serializable; + +/** + * The setup of the remote subscription. + * + * @author volsch + */ +public class RemoteSubscriptionSetup implements Serializable +{ + private static final long serialVersionUID = 7653552278753840057L; + + @Valid + private RemoteSubscriptionDhisSetup dhisSetup = new RemoteSubscriptionDhisSetup(); + + @Valid + private RemoteSubscriptionAdapterSetup adapterSetup = new RemoteSubscriptionAdapterSetup(); + + @Valid + private RemoteSubscriptionFhirSetup fhirSetup = new RemoteSubscriptionFhirSetup(); + + @Valid + private SystemUriSetup systemUriSetup = new SystemUriSetup(); + + public RemoteSubscriptionDhisSetup getDhisSetup() + { + return dhisSetup; + } + + public void setDhisSetup( RemoteSubscriptionDhisSetup dhisSetup ) + { + this.dhisSetup = dhisSetup; + } + + public RemoteSubscriptionAdapterSetup getAdapterSetup() + { + return adapterSetup; + } + + public void setAdapterSetup( RemoteSubscriptionAdapterSetup adapterSetup ) + { + this.adapterSetup = adapterSetup; + } + + public RemoteSubscriptionFhirSetup getFhirSetup() + { + return fhirSetup; + } + + public void setFhirSetup( RemoteSubscriptionFhirSetup fhirSetup ) + { + this.fhirSetup = fhirSetup; + } + + public SystemUriSetup getSystemUriSetup() + { + return systemUriSetup; + } + + public void setSystemUriSetup( SystemUriSetup systemUriSetup ) + { + this.systemUriSetup = systemUriSetup; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/Setup.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/Setup.java new file mode 100644 index 00000000..c57c19f8 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/Setup.java @@ -0,0 +1,68 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javax.validation.Valid; +import java.io.Serializable; + +/** + * The initial setup data of the adapter. + * + * @author volsch + */ +public class Setup implements Serializable +{ + private static final long serialVersionUID = -5257148949174992807L; + + @Valid + private RemoteSubscriptionSetup remoteSubscriptionSetup = new RemoteSubscriptionSetup(); + + @Valid + private OrganizationCodeSetup organizationCodeSetup = new OrganizationCodeSetup(); + + public RemoteSubscriptionSetup getRemoteSubscriptionSetup() + { + return remoteSubscriptionSetup; + } + + public void setRemoteSubscriptionSetup( RemoteSubscriptionSetup remoteSubscriptionSetup ) + { + this.remoteSubscriptionSetup = remoteSubscriptionSetup; + } + + public OrganizationCodeSetup getOrganizationCodeSetup() + { + return organizationCodeSetup; + } + + public void setOrganizationCodeSetup( OrganizationCodeSetup organizationCodeSetup ) + { + this.organizationCodeSetup = organizationCodeSetup; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupController.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupController.java new file mode 100644 index 00000000..092bb306 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupController.java @@ -0,0 +1,110 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.apache.commons.codec.binary.Hex; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; + +import javax.annotation.Nonnull; +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.SecureRandom; + +@Controller +@ConditionalOnProperty( "adapter-setup" ) +public class SetupController +{ + protected static final int BEARER_TOKEN_BYTES = 40; + + private final SetupService setupService; + + public SetupController( @Nonnull SetupService setupService ) + { + this.setupService = setupService; + } + + @GetMapping( "/setup" ) + public String display( @Nonnull Model model, @Nonnull HttpServletRequest servletRequest ) + { + final boolean completedSetup = setupService.hasCompletedSetup(); + if ( !completedSetup ) + { + final Setup setup = new Setup(); + setup.getRemoteSubscriptionSetup().getAdapterSetup().setBaseUrl( getAdapterBaseUrl( servletRequest ) ); + setup.getRemoteSubscriptionSetup().getAdapterSetup().setAuthorizationHeaderValue( createBearerTokenHeaderValue( servletRequest ) ); + + model.addAttribute( "setup", setup ); + } + + model.addAttribute( "completedSetup", completedSetup ); + return "setup"; + } + + @PostMapping( "/setup" ) + public String submit( @Valid Setup setup, BindingResult bindingResult, Model model ) + { + if ( bindingResult.hasErrors() ) + { + model.addAttribute( "completedSetup", false ); + return "setup"; + } + + setupService.apply( setup ); + return "result"; + } + + @Nonnull + protected String getAdapterBaseUrl( @Nonnull HttpServletRequest servletRequest ) + { + try + { + return new URL( servletRequest.getScheme(), servletRequest.getServerName(), servletRequest.getServerPort(), servletRequest.getContextPath() ).toString(); + } + catch ( MalformedURLException e ) + { + throw new IllegalStateException( "Could not construct base URL from servlet request.", e ); + } + } + + @Nonnull + protected String createBearerTokenHeaderValue( @Nonnull HttpServletRequest servletRequest ) + { + final SecureRandom secureRandom = new SecureRandom(); + final byte[] bytes = new byte[BEARER_TOKEN_BYTES]; + secureRandom.nextBytes( bytes ); + return "Bearer " + Hex.encodeHexString( bytes ); + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupException.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupException.java new file mode 100644 index 00000000..f85e1146 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupException.java @@ -0,0 +1,45 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Thrown if setup cannot be performed due to an error. The main cause for this + * error may be missing data in the database. + * + * @author volsch + */ +public class SetupException extends RuntimeException +{ + private static final long serialVersionUID = -5920949288192486489L; + + public SetupException( String message ) + { + super( message ); + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupService.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupService.java new file mode 100644 index 00000000..2fbca69c --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupService.java @@ -0,0 +1,247 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.apache.commons.lang3.StringUtils; +import org.dhis2.fhir.adapter.fhir.metadata.model.AuthenticationMethod; +import org.dhis2.fhir.adapter.fhir.metadata.model.Code; +import org.dhis2.fhir.adapter.fhir.metadata.model.CodeCategory; +import org.dhis2.fhir.adapter.fhir.metadata.model.FhirResourceType; +import org.dhis2.fhir.adapter.fhir.metadata.model.RemoteSubscription; +import org.dhis2.fhir.adapter.fhir.metadata.model.RemoteSubscriptionSystem; +import org.dhis2.fhir.adapter.fhir.metadata.model.RequestHeader; +import org.dhis2.fhir.adapter.fhir.metadata.model.SubscriptionAdapterEndpoint; +import org.dhis2.fhir.adapter.fhir.metadata.model.SubscriptionDhisEndpoint; +import org.dhis2.fhir.adapter.fhir.metadata.model.SubscriptionFhirEndpoint; +import org.dhis2.fhir.adapter.fhir.metadata.model.System; +import org.dhis2.fhir.adapter.fhir.metadata.model.SystemCode; +import org.dhis2.fhir.adapter.fhir.metadata.repository.CodeCategoryRepository; +import org.dhis2.fhir.adapter.fhir.metadata.repository.CodeRepository; +import org.dhis2.fhir.adapter.fhir.metadata.repository.RemoteSubscriptionRepository; +import org.dhis2.fhir.adapter.fhir.metadata.repository.SystemCodeRepository; +import org.dhis2.fhir.adapter.fhir.metadata.repository.SystemRepository; +import org.dhis2.fhir.adapter.fhir.model.FhirVersion; +import org.dhis2.fhir.adapter.fhir.security.SystemAuthenticationToken; +import org.springframework.data.domain.Example; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; +import java.util.stream.Collectors; + +/** + * The service that provides the initial setup. + * + * @author volsch + */ +@Service +public class SetupService +{ + public static final String ORGANIZATION_CODE_CATEGORY = "ORGANIZATION_UNIT"; + + private final CodeCategoryRepository codeCategoryRepository; + + private final CodeRepository codeRepository; + + private final SystemRepository systemRepository; + + private final SystemCodeRepository systemCodeRepository; + + private final RemoteSubscriptionRepository remoteSubscriptionRepository; + + public SetupService( @Nonnull CodeCategoryRepository codeCategoryRepository, @Nonnull CodeRepository codeRepository, + @Nonnull SystemRepository systemRepository, @Nonnull SystemCodeRepository systemCodeRepository, + @Nonnull RemoteSubscriptionRepository remoteSubscriptionRepository ) + { + this.codeCategoryRepository = codeCategoryRepository; + this.codeRepository = codeRepository; + this.systemRepository = systemRepository; + this.systemCodeRepository = systemCodeRepository; + this.remoteSubscriptionRepository = remoteSubscriptionRepository; + } + + public boolean hasCompletedSetup() + { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + SecurityContextHolder.getContext().setAuthentication( new SystemAuthenticationToken() ); + try + { + return (remoteSubscriptionRepository.count() > 0); + } + finally + { + SecurityContextHolder.getContext().setAuthentication( authentication ); + } + } + + @Transactional + public void apply( @Nonnull Setup setup ) + { + final System organizationSystem = findOrCreateSystem( + setup.getRemoteSubscriptionSetup().getSystemUriSetup().getOrganizationSystemUri(), + "Default DHIS2 Organization Units", "DEFAULT_DHIS2_ORG_UNIT", "Default DHIS2 organization unit system URI." ); + final System patientSystem = findOrCreateSystem( + setup.getRemoteSubscriptionSetup().getSystemUriSetup().getPatientSystemUri(), + "Default DHIS2 Patients", "DEFAULT_DHIS2_PATIENT", "Default DHIS2 patient system URI." ); + + createRemoteSubscription( setup.getRemoteSubscriptionSetup(), organizationSystem, patientSystem ); + createSystemCodes( setup.getOrganizationCodeSetup(), organizationSystem ); + } + + private void createSystemCodes( @Nonnull OrganizationCodeSetup setup, @Nonnull System organizationSystem ) + { + final List codeMappings = setup.getCodeMappings(); + if ( !codeMappings.isEmpty() ) + { + final CodeCategory organizationCodeCategory = findCodeCategory( ORGANIZATION_CODE_CATEGORY ); + final Map persistedCodes = codeRepository.saveAll( createCodes( codeMappings, organizationCodeCategory ).values() ) + .stream().collect( Collectors.toMap( Code::getMappedCode, c -> c ) ); + + final List systemCodes = codeMappings.stream().map( cm -> { + final SystemCode systemCode = new SystemCode(); + systemCode.setSystem( organizationSystem ); + systemCode.setSystemCode( cm.getFhirCode() ); + systemCode.setCode( persistedCodes.get( cm.getDhisCode() ) ); + return systemCode; + } ).collect( Collectors.toList() ); + systemCodeRepository.saveAll( systemCodes ); + } + } + + @Nonnull + private Map createCodes( @Nonnull List codeMappings, @Nonnull CodeCategory organizationCodeCategory ) + { + final Map codes = new HashMap<>(); + codeMappings.forEach( cm -> { + codes.computeIfAbsent( cm.getDhisCode(), c -> { + final Code code = new Code(); + code.setCodeCategory( organizationCodeCategory ); + code.setName( "Default DHIS2 Organization Unit Code " + c ); + code.setDescription( "Default DHIS2 organization unit code " + c ); + code.setCode( c.startsWith( OrganizationCodeSetup.DEFAULT_CODE_PREFIX ) ? c : + (OrganizationCodeSetup.DEFAULT_CODE_PREFIX + c) ); + code.setMappedCode( c ); + return code; + } ); + } ); + return codes; + } + + private void createRemoteSubscription( @Nonnull RemoteSubscriptionSetup setup, @Nonnull System organizationSystem, @Nonnull System patientSystem ) + { + final RemoteSubscription remoteSubscription = new RemoteSubscription(); + remoteSubscription.setSystems( new ArrayList<>() ); + remoteSubscription.setAutoCreatedSubscriptionResources( Collections.singleton( FhirResourceType.PATIENT ) ); + remoteSubscription.setName( "Default Remote Subscription" ); + remoteSubscription.setCode( "DEFAULT" ); + remoteSubscription.setDescription( "Default remote subscription." ); + remoteSubscription.setFhirVersion( FhirVersion.DSTU3 ); + remoteSubscription.setEnabled( true ); + remoteSubscription.setToleranceMillis( setup.getFhirSetup().getToleranceMillis() ); + + final SubscriptionAdapterEndpoint adapterEndpoint = new SubscriptionAdapterEndpoint(); + adapterEndpoint.setBaseUrl( setup.getAdapterSetup().getBaseUrl() ); + adapterEndpoint.setAuthorizationHeader( setup.getAdapterSetup().getAuthorizationHeaderValue() ); + adapterEndpoint.setSubscriptionType( setup.getFhirSetup().getSubscriptionType() ); + remoteSubscription.setAdapterEndpoint( adapterEndpoint ); + + final SubscriptionDhisEndpoint dhisEndpoint = new SubscriptionDhisEndpoint(); + dhisEndpoint.setAuthenticationMethod( AuthenticationMethod.BASIC ); + dhisEndpoint.setUsername( setup.getDhisSetup().getUsername() ); + dhisEndpoint.setPassword( setup.getDhisSetup().getPassword() ); + remoteSubscription.setDhisEndpoint( dhisEndpoint ); + + final SubscriptionFhirEndpoint fhirEndpoint = new SubscriptionFhirEndpoint(); + fhirEndpoint.setBaseUrl( setup.getFhirSetup().getBaseUrl() ); + if ( StringUtils.isNotBlank( setup.getFhirSetup().getHeaderName() ) && + StringUtils.isNotBlank( setup.getFhirSetup().getHeaderValue() ) ) + { + fhirEndpoint.setHeaders( new TreeSet<>( Collections.singleton( new RequestHeader( + setup.getFhirSetup().getHeaderName(), + setup.getFhirSetup().getHeaderValue(), true ) ) ) ); + } + remoteSubscription.setFhirEndpoint( fhirEndpoint ); + + final RemoteSubscriptionSystem subscriptionOrganizationSystem = new RemoteSubscriptionSystem(); + subscriptionOrganizationSystem.setRemoteSubscription( remoteSubscription ); + subscriptionOrganizationSystem.setFhirResourceType( FhirResourceType.ORGANIZATION ); + subscriptionOrganizationSystem.setSystem( organizationSystem ); + subscriptionOrganizationSystem.setCodePrefix( setup.getSystemUriSetup().getOrganizationCodePrefix() ); + remoteSubscription.getSystems().add( subscriptionOrganizationSystem ); + + final RemoteSubscriptionSystem subscriptionPatientSystem = new RemoteSubscriptionSystem(); + subscriptionPatientSystem.setRemoteSubscription( remoteSubscription ); + subscriptionPatientSystem.setFhirResourceType( FhirResourceType.PATIENT ); + subscriptionPatientSystem.setSystem( patientSystem ); + subscriptionPatientSystem.setCodePrefix( setup.getSystemUriSetup().getPatientCodePrefix() ); + remoteSubscription.getSystems().add( subscriptionPatientSystem ); + + remoteSubscriptionRepository.saveAndFlush( remoteSubscription ); + } + + @Nonnull + protected System findOrCreateSystem( @Nonnull String systemUri, @Nonnull String name, @Nonnull String code, @Nonnull String description ) + { + System system = new System(); + system.setSystemUri( systemUri ); + system = systemRepository.findAll( Example.of( system ) ).stream().findFirst().orElse( null ); + if ( system == null ) + { + system = new System(); + system.setName( name ); + system.setCode( code ); + system.setDescription( description ); + system.setSystemUri( systemUri ); + system.setEnabled( true ); + system = systemRepository.save( system ); + } + return system; + } + + @Nonnull + protected CodeCategory findCodeCategory( @Nonnull String code ) + { + CodeCategory codeCategory = new CodeCategory(); + codeCategory.setCode( code ); + codeCategory = codeCategoryRepository.findAll( Example.of( codeCategory ) ).stream().findFirst().orElse( null ); + if ( codeCategory == null ) + { + throw new SetupException( "Code category with code " + code + " does not exist." ); + } + return codeCategory; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/SystemUriSetup.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/SystemUriSetup.java new file mode 100644 index 00000000..ece929d7 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/SystemUriSetup.java @@ -0,0 +1,105 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.dhis2.fhir.adapter.fhir.metadata.model.RemoteSubscriptionSystem; +import org.dhis2.fhir.adapter.fhir.metadata.model.System; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.io.Serializable; + +/** + * The system URI setup data. + * + * @author volsch + */ +public class SystemUriSetup implements Serializable +{ + private static final long serialVersionUID = -4672868058462270107L; + + @Uri + @NotBlank( message = "Must not be blank." ) + @Size( max = System.MAX_SYSTEM_URI_LENGTH, message = "Must not be longer than {max} characters." ) + private String organizationSystemUri; + + @Size( max = RemoteSubscriptionSystem.MAX_CODE_PREFIX_LENGTH, message = "Code prefix must not be longer than {max} characters." ) + @Pattern( regexp = "[^:]*", message = "Code prefix must not contain colon characters." ) + private String organizationCodePrefix; + + @Uri + @NotBlank( message = "Must not be blank." ) + @Size( max = System.MAX_SYSTEM_URI_LENGTH, message = "Must not be longer than {max} characters." ) + private String patientSystemUri; + + @Size( max = RemoteSubscriptionSystem.MAX_CODE_PREFIX_LENGTH, message = "Code prefix must not be longer than {max} characters." ) + @Pattern( regexp = "[^:]*", message = "Code prefix must not contain colon characters." ) + private String patientCodePrefix; + + public String getOrganizationSystemUri() + { + return organizationSystemUri; + } + + public void setOrganizationSystemUri( String organizationSystemUri ) + { + this.organizationSystemUri = organizationSystemUri; + } + + public String getPatientSystemUri() + { + return patientSystemUri; + } + + public void setPatientSystemUri( String patientSystemUri ) + { + this.patientSystemUri = patientSystemUri; + } + + public String getOrganizationCodePrefix() + { + return organizationCodePrefix; + } + + public void setOrganizationCodePrefix( String organizationCodePrefix ) + { + this.organizationCodePrefix = organizationCodePrefix; + } + + public String getPatientCodePrefix() + { + return patientCodePrefix; + } + + public void setPatientCodePrefix( String patientCodePrefix ) + { + this.patientCodePrefix = patientCodePrefix; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/Uri.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/Uri.java new file mode 100644 index 00000000..8a344413 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/Uri.java @@ -0,0 +1,53 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Validation that a URI is a valid URI. + * + * @author volsch + */ +@Constraint( validatedBy = UriValidator.class ) +@Target( { ElementType.FIELD } ) +@Retention( RetentionPolicy.RUNTIME ) +public @interface Uri +{ + String message() default "Not a valid URI"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} \ No newline at end of file diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/UriValidator.java b/app/src/main/java/org/dhis2/fhir/adapter/setup/UriValidator.java new file mode 100644 index 00000000..d9fc87a8 --- /dev/null +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/UriValidator.java @@ -0,0 +1,63 @@ +package org.dhis2.fhir.adapter.setup; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Validator that checks that a URI is a valid URI. + * + * @author volsch + */ +public class UriValidator implements ConstraintValidator +{ + @Override + public boolean isValid( String value, ConstraintValidatorContext context ) + { + if ( value == null ) + { + return true; + } + + try + { + new URI( value ); + } + catch ( URISyntaxException e ) + { + context.buildConstraintViolationWithTemplate( "Not a valid URI." ); + return false; + } + + return true; + } +} diff --git a/app/src/main/resources/application.yml b/app/src/main/resources/application.yml index c0a1c1fe..1ee843c6 100644 --- a/app/src/main/resources/application.yml +++ b/app/src/main/resources/application.yml @@ -32,7 +32,7 @@ spring: write-dates-as-timestamps: false data: rest: - basePath: /api/1.00 + basePath: /api detection-strategy: annotated jpa: generate-ddl: false diff --git a/app/src/main/resources/default-application.yml b/app/src/main/resources/default-application.yml index 3aee29ca..97dbe619 100644 --- a/app/src/main/resources/default-application.yml +++ b/app/src/main/resources/default-application.yml @@ -73,6 +73,8 @@ dhis2.fhir-adapter: system-authentication: username: @dhis2.username@ password: @dhis2.password@ + connectTimeout: 5000 + readTimeout: 30000 security: authorities: administration: diff --git a/app/src/main/resources/static/favicon.ico b/app/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..58afa3ff47a562c94202afd8d6a164103024ca25 GIT binary patch literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x#lFaYJy!KziE%$p_^S&)O*P01Es z-(MiGvLXESf;`**q~OKHuK(9{re}ckFRS!@Ix9*0KPh-#rWscK>zgC~;{(gf+`#tW z(!Vgr^8cHM*TF_y-o5z$;YE!ocz%`{Ui~0HEXX$h|Ln%8|3AKb_`k6=3ah_x*$)eY zxoL*~@1NcC|M$=D|F`v}VVFTkKggUpDSH3!p5F2Q@9$s#cTLGdF@qTWFf*Q9JqC8i ziV6?78N})bnX#rW034oI_ADVv|Drsb|8r9H;cAzaxPbLPym$~UhRc4CzPFEW{r~X% z{{Q1Ey8a)WUki+j(_sB4R`n3n4|4D78vp+f&h7pG{qsApzHcAj{6D*C`v2L7sgcK_<;m}kKJ@PQP(w8H1p`mW5hhK2^F&6A5Y$iW*s%Xt6)XJBYx JBoBl10s!AwWElVe literal 0 HcmV?d00001 diff --git a/app/src/main/resources/templates/setup.html b/app/src/main/resources/templates/setup.html new file mode 100644 index 00000000..69e2a051 --- /dev/null +++ b/app/src/main/resources/templates/setup.html @@ -0,0 +1,175 @@ + + + + + + DHIS2 FHIR Adapter - Initial Setup + + + + +

DHIS2 FHIR Adapter - Initial Setup

+
+

The initial setup has already been completed and cannot be applied anymore. Please, use the REST API of the Adapter to apply any further configuration changes.

+
+
+

In the following sections you can enter the initial setup data of the Adapter. All further adjustments must be applied by using the REST API of the Adapter.

+
+

FHIR Subscriptions

+

This section contains the data that is used to setup the FHIR subscriptions.

+ +

DHIS2 Setup

+

This sub-section contains the data of DHIS2 that is required when storing mapped and transformed FHIR data on DHIS2.

+
+

The username and password of the user that is used to connect to DHIS2 and that creates and updates data on DHIS2. This user must have privileges to create and update tracked entity instances, + create and update Tracker program instances and create and update tracker program stage instances.

+

+

DHIS2 Username

+

+

DHIS2 Password

+
+ +

Adapter Setup

+

This sub-section contains the data of the Adapter that is required to setup the FHIR subscriptions.

+
+

The base URL of the adapter that is used by the FHIR server to send subscription notifications. If the FHIR server does not run on the same machine like the Adapter, the URL must not include localhost.
+ Example: http://localhost:8081

+

+

Adapter Base URL

+
+
+

The authorization header value that is used by the FHIR server to authenticate at the Adapter. The already entered value can be taken for this purpose and does not need to be changed normally.
+ Example: Bearer 88e26f25fcd81027f5955941e456223cd9c226fcb3e7d628b0

+

+

Adapter Authorization Header Value

+
+ +

FHIR Setup

+

This sub-section contains the data of the FHIR server that is required by the Adapter to access the FHIR server and to create the FHIR subscriptions. When this form is submitted the Adapter will create a subscription for FHIR resource Patient on FHIR + server.

+
+

The base URL of the FHIR endpoints that support version DSTU3.
+ Example: http://localhost:8082/hapi-fhir-jpaserver-example/baseDstu3

+

+

FHIR Base URL

+
+
+

The header name and header value to be set when accessing the FHIR endpoints. The server may require for example a authorization header with Basic or Bearer Token authorization. + If any if the input fields contain a blank value, no header will be sent.
+ Example Name (first input field): Authorization
+ Example Value (second input field): Bearer 9b2f-9a14-cac3-e82c-01d0-a9168c

+

+

+

FHIR Header Name

+

FHIR Header Value

+
+
+

The type of subscription that is used. Even if the Adapter requests data based on the last updated timestamp of the resources when it receives a notification about created or updated data on the FHIR server, it also supports notifications that + contain a payload. If there is any issue with the subscription notification that does not include a payload, the notification that includes a payload should be used. If HAPI FHIR JPA Server 3.5.0 (and may be later versions as well) are used, the + subscription that uses a payload must be selected (preselected below already). If the FHIR server has no known issues with a subscription without a payload, this type of subscription should be selected since it requires less resources on the FHIR + server.

+

+

+

FHIR Subscription Type

+
+
+

The data from FHIR servers is retrieved based on its last update timestamp. The clocks on all servers should be synchronized. Due to clock inaccuracies and concurrent updates that might not yet been visible, at least 5 additional overlapping seconds + should be given when retrieving the data.

+

ms

+

Tolerance Millis Errors

+
+ +

System URI Setup

+

To identify FHIR organization and FHIR patient resources they must have a unique business identifier (FHIR identifier property). These unique business identifiers are defined for a specific system URI. If several FHIR servers are connected to the + adapter, these URIs may differ for each connected FHIR server. This basic setup page allows only to connect one FHIR server.
+ Optionally a code prefix can be specified (second input field) that contains the prefix for the value in DHIS2. + E.g. if the FHIR business identifier is 4711 and the code prefix is OU_ then the organization unit code OU_4711 will match on DHIS2.

+
+

The following system URI is used to extract the business identifier from FHIR organization resources.
+ Example: http://example.sl/organizations

+

+

+

Organization System URI

+
+
+

The following system URI is used to extract the business identifier from FHIR patient resources.
+ Example: http://example.sl/patients

+

+

+

Patient System URI

+

Patient Code Prefix

+
+ +

Code Mappings

+

This section contains the data that is used to map codes included in FHIR resources to codes that are used on DHIS2.

+

FHIR Organization to DHIS2 Organization Unit Mapping

+

The mapping uses the business identifier code that is defined by the FHIR organization resource and tries to lookup the code in the mapping table that is defined below. If the code cannot be found in the mapping table below, the code is optionally + be used used to lookup the DHIS2 organization unit (including the code prefix that has been defined above) as a fallback. If such a DHIS2 organization unit does not exist, the default organization unit is used (when specified). If no existing DHIS2 + organization unit can be found, the transformation of the input data will be skipped.

+

The mapping table below has two columns. The column values may be separated by spaces, tabulators, commas and semicolons. Every code mapping must be placed in a new line. The first column contains the FHIR business identifier code and the second + column contains the DHIS2 organization unit code. The same FHIR identifier code must not be specified more than once. DHIS2 organization unit codes can be specified several times.

+ Example:

+
+      4711 OU_1394
+      8912;OU_6253
+      7352,OU_7262
+    
+

+

Org Code Mappings

+

+

+

Org Code Default

+ +

After pressing the button you will be asked for a username and password. Please, enter the username and password of a DHIS2 user that has all privileges.

+

+
+
+ + diff --git a/common/src/main/java/org/dhis2/fhir/adapter/cache/AbstractSimpleCacheConfig.java b/common/src/main/java/org/dhis2/fhir/adapter/cache/AbstractSimpleCacheConfig.java index 54c75a11..420a1069 100644 --- a/common/src/main/java/org/dhis2/fhir/adapter/cache/AbstractSimpleCacheConfig.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/cache/AbstractSimpleCacheConfig.java @@ -29,6 +29,7 @@ */ import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.cache.support.NoOpCacheManager; @@ -58,12 +59,14 @@ public abstract class AbstractSimpleCacheConfig implements Serializable private SimpleCacheType type = SimpleCacheType.NONE; @Valid + @NestedConfigurationProperty @NotNull - private SimpleCaffeineCacheConfig caffeine = new SimpleCaffeineCacheConfig(); + private final SimpleCaffeineCacheConfig caffeine = new SimpleCaffeineCacheConfig(); @Valid + @NestedConfigurationProperty @NotNull - private SimpleRedisCacheConfig redis = new SimpleRedisCacheConfig(); + private final SimpleRedisCacheConfig redis = new SimpleRedisCacheConfig(); @Nonnull public SimpleCacheType getType() @@ -82,22 +85,12 @@ public SimpleCaffeineCacheConfig getCaffeine() return caffeine; } - public void setCaffeine( @Nonnull SimpleCaffeineCacheConfig caffeine ) - { - this.caffeine = caffeine; - } - @Nonnull public SimpleRedisCacheConfig getRedis() { return redis; } - public void setRedis( @Nonnull SimpleRedisCacheConfig redis ) - { - this.redis = redis; - } - @Nonnull protected CacheManager createCacheManager( @Nonnull ObjectProvider redisConnectionFactoryProvider, @Nonnull RedisSerializer redisSerializer ) { @@ -119,7 +112,7 @@ protected CacheManager createCacheManager( @Nonnull ObjectProvider RedisCacheConfiguration createRedisCacheConfiguration( @Nonnull RedisSerializer redisSerializer ) { - return RedisCacheConfiguration.defaultCacheConfig().computePrefixWith( getRedis().getKeyPrefix() ) + return RedisCacheConfiguration.defaultCacheConfig().computePrefixWith( getRedis().getCacheKeyPrefix() ) .entryTtl( getRedis().getTimeToLive() ).serializeValuesWith( new RedisSerializationContext.SerializationPair() { @Override diff --git a/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleRedisCacheConfig.java b/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleRedisCacheConfig.java index 9f2a7aa6..e9e2c869 100644 --- a/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleRedisCacheConfig.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleRedisCacheConfig.java @@ -35,6 +35,7 @@ import javax.validation.constraints.NotNull; import java.io.Serializable; import java.time.Duration; +import java.util.UUID; /** * Simple cache configuration of a Redis cache. @@ -50,7 +51,7 @@ public class SimpleRedisCacheConfig implements Serializable private Duration timeToLive = Duration.ZERO; @NotNull - private CacheKeyPrefix keyPrefix = CacheKeyPrefix.simple(); + private String keyPrefix = UUID.randomUUID().toString(); @Nonnull public Duration getTimeToLive() @@ -64,14 +65,9 @@ public void setTimeToLive( @Nonnull Duration timeToLive ) } @Nonnull - public CacheKeyPrefix getKeyPrefix() + public CacheKeyPrefix getCacheKeyPrefix() { - return keyPrefix; - } - - public void setKeyPrefix( @Nonnull String keyPrefix ) - { - this.keyPrefix = new CacheKeyPrefix() + return new CacheKeyPrefix() { @Override @Nonnull @@ -81,4 +77,20 @@ public String compute( @Nonnull String cacheName ) } }; } + + @Nonnull + public String getKeyPrefix() + { + return keyPrefix; + } + + public void setKeyPrefix( @Nonnull String keyPrefix ) + { + this.keyPrefix = keyPrefix; + } + + public void setKeyPrefixString( @Nonnull String keyPrefix ) + { + this.keyPrefix = keyPrefix; + } } diff --git a/common/src/main/java/org/dhis2/fhir/adapter/lock/LockContext.java b/common/src/main/java/org/dhis2/fhir/adapter/lock/LockContext.java index 1c202d22..5f3edc04 100644 --- a/common/src/main/java/org/dhis2/fhir/adapter/lock/LockContext.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/lock/LockContext.java @@ -38,9 +38,9 @@ public interface LockContext extends AutoCloseable { /** - * Locks the specified key. The method may block until the key can be locked. + * Locks the specified cache. The method may block until the cache can be locked. * - * @param key the key that should be locked. + * @param key the cache that should be locked. */ void lock( @Nonnull String key ); diff --git a/common/src/main/java/org/dhis2/fhir/adapter/lock/LockException.java b/common/src/main/java/org/dhis2/fhir/adapter/lock/LockException.java index 936aa330..045fb575 100644 --- a/common/src/main/java/org/dhis2/fhir/adapter/lock/LockException.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/lock/LockException.java @@ -29,7 +29,7 @@ */ /** - * Thrown if there is any error when locking a key. + * Thrown if there is any error when locking a cache. * * @author volsch */ diff --git a/common/src/main/java/org/dhis2/fhir/adapter/lock/impl/LockContextImpl.java b/common/src/main/java/org/dhis2/fhir/adapter/lock/impl/LockContextImpl.java index cbcf1407..79862cc0 100644 --- a/common/src/main/java/org/dhis2/fhir/adapter/lock/impl/LockContextImpl.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/lock/impl/LockContextImpl.java @@ -121,7 +121,7 @@ public void lock( @Nonnull String key ) } catch ( SQLException e ) { - throw new LockException( "Could not lock key " + key + " due to a technical error.", e ); + throw new LockException( "Could not lock cache " + key + " due to a technical error.", e ); } lockedKeys.add( key ); diff --git a/common/src/main/java/org/dhis2/fhir/adapter/queue/QueueConfig.java b/common/src/main/java/org/dhis2/fhir/adapter/queue/QueueConfig.java index 170bb1d4..ab1cb666 100644 --- a/common/src/main/java/org/dhis2/fhir/adapter/queue/QueueConfig.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/queue/QueueConfig.java @@ -29,6 +29,7 @@ */ import org.apache.activemq.artemis.core.settings.impl.AddressSettings; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.validation.annotation.Validated; import javax.validation.Valid; @@ -48,9 +49,11 @@ public class QueueConfig implements Serializable @NotBlank private String queueName; + @NestedConfigurationProperty @Valid private QueueListenerConfig listener = new QueueListenerConfig(); + @NestedConfigurationProperty @Valid private AddressSettings embeddedAddressSettings = new AddressSettings(); diff --git a/common/src/main/java/org/dhis2/fhir/adapter/script/FatalScriptCompilationException.java b/common/src/main/java/org/dhis2/fhir/adapter/script/FatalScriptCompilationException.java new file mode 100644 index 00000000..82aa7e1e --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/script/FatalScriptCompilationException.java @@ -0,0 +1,51 @@ +package org.dhis2.fhir.adapter.script; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Thrown if the script could not be compiled due to any error with the script + * compilation system. This exception will not be thrown if there are just syntax + * errors in the script. + * + * @author volsch + */ +public class FatalScriptCompilationException extends RuntimeException +{ + private static final long serialVersionUID = -3544954549662369603L; + + public FatalScriptCompilationException( String message ) + { + super( message ); + } + + public FatalScriptCompilationException( String message, Throwable cause ) + { + super( message, cause ); + } +} diff --git a/common/src/main/java/org/dhis2/fhir/adapter/script/ScriptCompilationException.java b/common/src/main/java/org/dhis2/fhir/adapter/script/ScriptCompilationException.java new file mode 100644 index 00000000..74be5bd2 --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/script/ScriptCompilationException.java @@ -0,0 +1,44 @@ +package org.dhis2.fhir.adapter.script; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Thrown if the script could not be compiled due to syntactically errors. + * + * @author volsch + */ +public class ScriptCompilationException extends RuntimeException +{ + private static final long serialVersionUID = -3544954549662369603L; + + public ScriptCompilationException( String message, Throwable cause ) + { + super( message, cause ); + } +} diff --git a/common/src/main/java/org/dhis2/fhir/adapter/script/ScriptCompiler.java b/common/src/main/java/org/dhis2/fhir/adapter/script/ScriptCompiler.java new file mode 100644 index 00000000..eca8cba1 --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/script/ScriptCompiler.java @@ -0,0 +1,51 @@ +package org.dhis2.fhir.adapter.script; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javax.annotation.Nonnull; + +/** + * Compiles a script with a specific script engine. The sole purpose of this is + * to check for errors in the script. + * + * @author volsch + */ +public interface ScriptCompiler +{ + /** + * Compiles the script. Compiling scripts may not be supported. + * + * @param script the script that should be compiled. + * @return true if the script has been compiled and no error has + * been detected, false if script compilation is not supported. + * @throws ScriptCompilationException thrown if the script has syntactical errors + * or cannot be compiled due to other issues. + */ + boolean compile( @Nonnull String script ) throws ScriptCompilationException; +} diff --git a/common/src/main/java/org/dhis2/fhir/adapter/script/impl/ScriptCompilerImpl.java b/common/src/main/java/org/dhis2/fhir/adapter/script/impl/ScriptCompilerImpl.java new file mode 100644 index 00000000..41fa89fd --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/script/impl/ScriptCompilerImpl.java @@ -0,0 +1,92 @@ +package org.dhis2.fhir.adapter.script.impl; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.dhis2.fhir.adapter.script.FatalScriptCompilationException; +import org.dhis2.fhir.adapter.script.ScriptCompilationException; +import org.dhis2.fhir.adapter.script.ScriptCompiler; + +import javax.annotation.Nonnull; +import javax.script.Compilable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +/** + * Standard implementation of {@link ScriptCompiler}. + */ +public class ScriptCompilerImpl implements ScriptCompiler +{ + private final String scriptEngineName; + + public ScriptCompilerImpl( @Nonnull String scriptEngineName ) + { + this.scriptEngineName = scriptEngineName; + } + + @Override + public boolean compile( @Nonnull String script ) + { + final ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + final ScriptEngine scriptEngine = scriptEngineManager.getEngineByName( scriptEngineName ); + if ( scriptEngine == null ) + { + throw new FatalScriptCompilationException( "Script engine has not been configured: " + scriptEngineName ); + } + + if ( !(scriptEngine instanceof Compilable) ) + { + return false; + } + final Compilable compilable = (Compilable) scriptEngine; + + try + { + compilable.compile( script ); + } + catch ( ScriptException e ) + { + final StringBuilder message = new StringBuilder( e.getMessage() ); + if ( e.getFileName() == null ) + { + if ( e.getLineNumber() != -1 ) + { + message.append( " at line number " ).append( e.getLineNumber() ); + } + if ( e.getColumnNumber() != -1 ) + { + message.append( " at column number " ).append( e.getColumnNumber() ); + } + } + throw new ScriptCompilationException( message.toString(), e ); + } + + return true; + } +} diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisConfig.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisConfig.java index 99e0116a..dd699e82 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisConfig.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisConfig.java @@ -80,7 +80,9 @@ public class DhisConfig @Nonnull public RestTemplate userDhis2RestTemplate( @Nonnull RestTemplateBuilder builder, @Nonnull DhisEndpointConfig endpointConfig, @Nonnull AuthorizationContext authorizationContext ) { - return builder.rootUri( getRootUri( endpointConfig, false ) ).configure( new AuthorizedRestTemplate( authorizationContext ) ); + return builder.rootUri( getRootUri( endpointConfig, false ) ) + .setConnectTimeout( endpointConfig.getConnectTimeout() ).setReadTimeout( endpointConfig.getReadTimeout() ) + .configure( new AuthorizedRestTemplate( authorizationContext ) ); } /** @@ -94,8 +96,9 @@ public RestTemplate userDhis2RestTemplate( @Nonnull RestTemplateBuilder builder, @Nonnull public RestTemplate systemDhis2RestTemplate( @Nonnull RestTemplateBuilder builder, @Nonnull DhisEndpointConfig endpointConfig ) { - return builder.rootUri( getRootUri( endpointConfig, false ) ).basicAuthorization( - endpointConfig.getSystemAuthentication().getUsername(), endpointConfig.getSystemAuthentication().getPassword() ).build(); + return builder.rootUri( getRootUri( endpointConfig, false ) ) + .setConnectTimeout( endpointConfig.getConnectTimeout() ).setReadTimeout( endpointConfig.getReadTimeout() ) + .basicAuthorization( endpointConfig.getSystemAuthentication().getUsername(), endpointConfig.getSystemAuthentication().getPassword() ).build(); } /** diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisEndpointConfig.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisEndpointConfig.java index 26a39930..44b88ba1 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisEndpointConfig.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisEndpointConfig.java @@ -30,6 +30,8 @@ import org.dhis2.fhir.adapter.model.UsernamePassword; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.context.annotation.Configuration; import org.springframework.validation.annotation.Validated; @@ -38,6 +40,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Positive; import java.io.Serializable; /** @@ -49,12 +52,17 @@ * @author volsch */ @Configuration +@EnableConfigurationProperties @ConfigurationProperties( "dhis2.fhir-adapter.endpoint" ) @Validated public class DhisEndpointConfig implements Serializable { private static final long serialVersionUID = 8393014237402428126L; + public static final int DEFAULT_CONNECT_TIMEOUT = 5_000; + + public static final int DEFAULT_READ_TIMEOUT = 30_000; + @NotBlank private String url; @@ -62,8 +70,15 @@ public class DhisEndpointConfig implements Serializable private String apiVersion; @NotNull + @NestedConfigurationProperty @Valid - private UsernamePassword systemAuthentication; + private UsernamePassword systemAuthentication = new UsernamePassword(); + + @Positive + private int connectTimeout = DEFAULT_CONNECT_TIMEOUT; + + @Positive + private int readTimeout = DEFAULT_READ_TIMEOUT; public String getUrl() { @@ -95,4 +110,24 @@ public void setSystemAuthentication( @NotNull @Valid @Nonnull UsernamePassword s { this.systemAuthentication = systemAuthentication; } + + public int getConnectTimeout() + { + return connectTimeout; + } + + public void setConnectTimeout( int connectTimeout ) + { + this.connectTimeout = connectTimeout; + } + + public int getReadTimeout() + { + return readTimeout; + } + + public void setReadTimeout( int readTimeout ) + { + this.readTimeout = readTimeout; + } } diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisMetadataCacheConfig.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisMetadataCacheConfig.java index 7b485ce3..1906e193 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisMetadataCacheConfig.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisMetadataCacheConfig.java @@ -31,6 +31,7 @@ import org.dhis2.fhir.adapter.cache.AbstractSimpleCacheConfig; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -43,8 +44,11 @@ /** * Cache configuration for FHIR Resources. + * + * @author volsch */ @Configuration +@EnableConfigurationProperties @ConfigurationProperties( "dhis2.fhir-adapter.cache.dhis" ) @Validated public class DhisMetadataCacheConfig extends AbstractSimpleCacheConfig diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/security/DhisWebApiAuthenticationProvider.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/security/DhisWebApiAuthenticationProvider.java index 1d40221d..4cc6df02 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/security/DhisWebApiAuthenticationProvider.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/security/DhisWebApiAuthenticationProvider.java @@ -72,7 +72,8 @@ public class DhisWebApiAuthenticationProvider extends AbstractUserDetailsAuthent public DhisWebApiAuthenticationProvider( @Nonnull RestTemplateBuilder restTemplateBuilder, @Nonnull DhisEndpointConfig endpointConfig, @Nonnull SecurityConfig securityConfig ) { - this.restTemplateBuilder = restTemplateBuilder.rootUri( DhisConfig.getRootUri( endpointConfig, true ) ); + this.restTemplateBuilder = restTemplateBuilder.rootUri( DhisConfig.getRootUri( endpointConfig, true ) ) + .setConnectTimeout( endpointConfig.getConnectTimeout() ).setReadTimeout( endpointConfig.getReadTimeout() ); this.securityConfig = securityConfig; } diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/security/SecurityConfig.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/security/SecurityConfig.java index d02dbd4f..fd697b0e 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/security/SecurityConfig.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/security/SecurityConfig.java @@ -41,7 +41,7 @@ public interface SecurityConfig { /** - * Returns the authorities mappings where the key is the DHIS2 authority and + * Returns the authorities mappings where the cache is the DHIS2 authority and * the values are the Adapter authorities to be grantes. * * @return the mapping of authorities. diff --git a/fhir-dstu3/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/dstu3/Dstu3CodeFhirToDhisTransformerUtils.java b/fhir-dstu3/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/dstu3/Dstu3CodeFhirToDhisTransformerUtils.java index 5a8a94b8..f0b6c838 100644 --- a/fhir-dstu3/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/dstu3/Dstu3CodeFhirToDhisTransformerUtils.java +++ b/fhir-dstu3/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/dstu3/Dstu3CodeFhirToDhisTransformerUtils.java @@ -31,6 +31,7 @@ import com.google.common.collect.Sets; import org.dhis2.fhir.adapter.Scriptable; import org.dhis2.fhir.adapter.fhir.metadata.model.SystemCode; +import org.dhis2.fhir.adapter.fhir.metadata.repository.CodeRepository; import org.dhis2.fhir.adapter.fhir.metadata.repository.SystemCodeRepository; import org.dhis2.fhir.adapter.fhir.model.FhirVersion; import org.dhis2.fhir.adapter.fhir.model.SystemCodeValue; @@ -66,9 +67,10 @@ public class Dstu3CodeFhirToDhisTransformerUtils extends AbstractCodeFhirToDhisT private final SystemCodeRepository systemCodeRepository; public Dstu3CodeFhirToDhisTransformerUtils( @Nonnull ScriptExecutionContext scriptExecutionContext, + @Nonnull CodeRepository codeRepository, @Nonnull SystemCodeRepository systemCodeRepository ) { - super( scriptExecutionContext ); + super( scriptExecutionContext, codeRepository ); this.systemCodeRepository = systemCodeRepository; } diff --git a/fhir/pom.xml b/fhir/pom.xml index cf284783..7ad5e630 100644 --- a/fhir/pom.xml +++ b/fhir/pom.xml @@ -93,6 +93,7 @@ ${db.url} ${db.username} ${db.password} + ${db.flyway.locations} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/AbstractRule.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/AbstractRule.java index b014d355..6bcecb13 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/AbstractRule.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/AbstractRule.java @@ -59,10 +59,14 @@ @Table( name = "fhir_rule" ) @Inheritance( strategy = InheritanceType.JOINED ) @DiscriminatorColumn( name = "dhis_resource_type", discriminatorType = DiscriminatorType.STRING ) -@NamedQueries( { @NamedQuery( name = AbstractRule.FIND_RULES_BY_TYPE_NAMED_QUERY, query = "SELECT r FROM AbstractRule r " + - "WHERE r.fhirResourceType=:fhirResourceType AND r.applicableCodeSet IS NULL AND r.enabled=true" ), - @NamedQuery( name = AbstractRule.FIND_RULES_BY_TYPE_CODES_NAMED_QUERY, query = "SELECT r FROM AbstractRule r JOIN r.applicableCodeSet acs JOIN acs.codeSetValues csv ON csv.enabled=true JOIN csv.id.code c " + - "JOIN c.systemCodes sc ON sc.systemCode=:code JOIN sc.system s ON s.systemUri=:system AND s.enabled=true WHERE r.fhirResourceType=:fhirResourceType AND r.enabled=true" ) } ) +@NamedQueries( { + @NamedQuery( name = AbstractRule.FIND_RULES_BY_TYPE_NAMED_QUERY, query = "SELECT r FROM AbstractRule r " + + "WHERE r.fhirResourceType=:fhirResourceType AND r.applicableCodeSet IS NULL AND r.enabled=true" ), + @NamedQuery( name = AbstractRule.FIND_RULES_BY_TYPE_CODES_NAMED_QUERY, query = + "SELECT r FROM AbstractRule r WHERE r.fhirResourceType=:fhirResourceType AND r.enabled=true AND " + + "(r.applicableCodeSet IS NULL OR (r.applicableCodeSet IS NOT NULL AND EXISTS " + + "(SELECT 1 FROM CodeSetValue csv JOIN csv.id.code c JOIN c.systemCodes sc ON sc.systemCodeValue IN (:systemCodeValues) " + + "JOIN sc.system s ON s.enabled=true WHERE csv.id.codeSet=r.applicableCodeSet AND csv.enabled=true)))" ) } ) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "dhisResourceType" ) @JsonSubTypes( { @JsonSubTypes.Type( value = TrackedEntityRule.class, name = "TRACKED_ENTITY" ), diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Code.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Code.java index c0c43e9e..e8e0ee45 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Code.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Code.java @@ -62,8 +62,11 @@ public class Code extends VersionedBaseMetadata implements Serializable public static final int MAX_CODE_LENGTH = 50; + public static final int MAX_MAPPED_CODE_LENGTH = 50; + private String name; private String code; + private String mappedCode; private String description; private CodeCategory codeCategory; private List systemCodes; @@ -92,6 +95,18 @@ public void setCode( String code ) this.code = code; } + @Basic + @Column( name = "mapped_code", length = MAX_MAPPED_CODE_LENGTH ) + public String getMappedCode() + { + return mappedCode; + } + + public void setMappedCode( String mappedCode ) + { + this.mappedCode = mappedCode; + } + @Basic @Column( name = "description", length = -1 ) public String getDescription() diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/System.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/System.java index 42994c93..df072bf0 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/System.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/System.java @@ -44,6 +44,8 @@ public class System extends VersionedBaseMetadata implements Serializable public static final int MAX_CODE_LENGTH = 50; + public static final int MAX_SYSTEM_URI_LENGTH = 120; + private String name; private String code; private String systemUri; @@ -76,7 +78,7 @@ public void setCode( String code ) } @Basic - @Column( name = "system_uri", nullable = false, length = 140 ) + @Column( name = "system_uri", updatable = false, nullable = false, length = 120 ) public String getSystemUri() { return systemUri; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SystemCode.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SystemCode.java index 2c6701ea..ce6c9807 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SystemCode.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SystemCode.java @@ -37,6 +37,7 @@ import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.PrePersist; import javax.persistence.Table; import javax.persistence.Transient; import java.io.Serializable; @@ -62,6 +63,7 @@ public class SystemCode extends VersionedBaseMetadata implements Serializable private System system; private String systemCode; private Code code; + private String systemCodeValue; @Basic @Column( name = "system_code", nullable = false, length = 120 ) @@ -99,11 +101,29 @@ public void setSystem( System system ) this.system = system; } + @Basic + @Column( name = "system_code_value", nullable = false, length = 241 ) + public String getSystemCodeValue() + { + return systemCodeValue; + } + + public void setSystemCodeValue( String systemCodeValue ) + { + this.systemCodeValue = systemCodeValue; + } + @Transient @JsonIgnore @Nonnull - public SystemCodeValue getSystemCodeValue() + public SystemCodeValue getCalculatedSystemCodeValue() { return new SystemCodeValue( getSystem().getSystemUri(), getSystemCode() ); } + + @PrePersist + protected void prePersist() + { + setSystemCodeValue( getSystem().getSystemUri() + SystemCodeValue.SEPARATOR + getSystemCode() ); + } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/VersionedBaseMetadata.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/VersionedBaseMetadata.java index 0c7028f2..05a07682 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/VersionedBaseMetadata.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/VersionedBaseMetadata.java @@ -29,6 +29,7 @@ */ import org.dhis2.fhir.adapter.fhir.metadata.repository.FhirAdapterMetadata; +import org.dhis2.fhir.adapter.fhir.security.AdapterSecurityUtils; import org.hibernate.annotations.GenericGenerator; import org.springframework.data.annotation.LastModifiedDate; @@ -37,6 +38,8 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.MappedSuperclass; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; import javax.persistence.Version; import java.io.Serializable; import java.time.LocalDateTime; @@ -121,4 +124,19 @@ public void setLastUpdatedAt( LocalDateTime lastUpdatedAt ) { this.lastUpdatedAt = lastUpdatedAt; } + + @PrePersist + protected void onPrePersist() + { + setCreatedAt( LocalDateTime.now() ); + setLastUpdatedAt( getCreatedAt() ); + setLastUpdatedBy( AdapterSecurityUtils.getCurrentUsername() ); + } + + @PreUpdate + protected void onPreUpdate() + { + setLastUpdatedAt( LocalDateTime.now() ); + setLastUpdatedBy( AdapterSecurityUtils.getCurrentUsername() ); + } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeRepository.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeRepository.java index a7c9ed67..d33692bf 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeRepository.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeRepository.java @@ -32,8 +32,12 @@ import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.rest.core.annotation.RestResource; import org.springframework.security.access.prepost.PreAuthorize; import javax.annotation.Nonnull; @@ -50,6 +54,12 @@ @PreAuthorize( "hasRole('CODE_MAPPING')" ) public interface CodeRepository extends JpaRepository { + @RestResource( exported = false ) + @Nonnull + @Cacheable( key = "{#root.methodName, #a0, #a1}" ) + @Query( "SELECT c FROM #{#entityName} c JOIN c.systemCodes sc ON sc.systemCode=:systemCode JOIN sc.system s ON s.systemUri=:systemUri AND s.enabled=true" ) + List findBySystemCode( @Param( "systemUri" ) @Nonnull String systemUri, @Param( "systemCode" ) @Nonnull String systemCode ); + @Override @Nonnull @CacheEvict( allEntries = true ) diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionRepository.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionRepository.java index 2bc6522a..ef55cc11 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionRepository.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionRepository.java @@ -31,6 +31,7 @@ import org.dhis2.fhir.adapter.fhir.metadata.model.RemoteSubscription; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -60,6 +61,16 @@ public interface RemoteSubscriptionRepository extends JpaRepository findOneByIdCached( @Param( "id" ) UUID id ); + @Override + @CachePut( key = "#a0.id" ) + @Nonnull + S saveAndFlush( @Nonnull S entity ); + + @Override + @CachePut( key = "#a0.id" ) + @Nonnull + S save( @Nonnull S entity ); + @Override @Nonnull @CacheEvict( allEntries = true, cacheNames = { "remoteSubscription", "remoteSubscriptionResource" } ) diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/key/RuleFindAllByInputDataKeyGenerator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/cache/RuleFindAllByInputDataKeyGenerator.java similarity index 95% rename from fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/key/RuleFindAllByInputDataKeyGenerator.java rename to fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/cache/RuleFindAllByInputDataKeyGenerator.java index 937ce0cd..a3a4b618 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/key/RuleFindAllByInputDataKeyGenerator.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/cache/RuleFindAllByInputDataKeyGenerator.java @@ -1,4 +1,4 @@ -package org.dhis2.fhir.adapter.fhir.metadata.repository.key; +package org.dhis2.fhir.adapter.fhir.metadata.repository.cache; /* * Copyright (c) 2004-2018, University of Oslo @@ -43,7 +43,7 @@ /** * Key generator for {@link org.dhis2.fhir.adapter.fhir.metadata.repository.CustomRuleRepository#findAllByInputData(FhirResourceType, Collection)}. - * Since key may be serialized to external storage, key is automatically a string representation. + * Since cache may be serialized to external storage, cache is automatically a string representation. * * @author volsch */ diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/key/SystemCodeFindAllByCodesKeyGenerator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/cache/SystemCodeFindAllByCodesKeyGenerator.java similarity index 94% rename from fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/key/SystemCodeFindAllByCodesKeyGenerator.java rename to fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/cache/SystemCodeFindAllByCodesKeyGenerator.java index f56ef704..9a92a636 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/key/SystemCodeFindAllByCodesKeyGenerator.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/cache/SystemCodeFindAllByCodesKeyGenerator.java @@ -1,4 +1,4 @@ -package org.dhis2.fhir.adapter.fhir.metadata.repository.key; +package org.dhis2.fhir.adapter.fhir.metadata.repository.cache; /* * Copyright (c) 2004-2018, University of Oslo @@ -38,7 +38,7 @@ /** * Key generator for {@link org.dhis2.fhir.adapter.fhir.metadata.repository.SystemCodeRepository#findAllByCodes(Collection)}. - * Since key may be serialized to external storage, key is automatically a string representation. + * Since cache may be serialized to external storage, cache is automatically a string representation. * * @author volsch */ diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/AdapterMetadataCacheConfig.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/AdapterMetadataCacheConfig.java index 2079fa44..598fcc29 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/AdapterMetadataCacheConfig.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/AdapterMetadataCacheConfig.java @@ -31,6 +31,7 @@ import org.dhis2.fhir.adapter.cache.AbstractSimpleCacheConfig; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -42,8 +43,11 @@ /** * Cache configuration for adapter metadata. + * + * @author volsch */ @Configuration +@EnableConfigurationProperties @ConfigurationProperties( "dhis2.fhir-adapter.cache.metadata" ) @Validated public class AdapterMetadataCacheConfig extends AbstractSimpleCacheConfig diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomRemoteSubscriptionRepositoryImpl.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomRemoteSubscriptionRepositoryImpl.java index b9e78d25..5de6f154 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomRemoteSubscriptionRepositoryImpl.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomRemoteSubscriptionRepositoryImpl.java @@ -42,6 +42,7 @@ import javax.annotation.Nonnull; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -127,16 +128,13 @@ protected List createAutoCreatedSubscriptionResource { final RemoteSubscriptionResource rsr = new RemoteSubscriptionResource(); rsr.setRemoteSubscription( remoteSubscription ); - rsr.setCreatedAt( remoteSubscription.getLastUpdatedAt() ); - rsr.setLastUpdatedBy( remoteSubscription.getLastUpdatedBy() ); - rsr.setLastUpdatedAt( remoteSubscription.getLastUpdatedAt() ); rsr.setFhirResourceType( resourceType ); rsr.setDescription( "Automatically created subscription for FHIR Resource " + resourceType.getResourceTypeName() + "." ); remoteSubscription.getResources().add( rsr ); final RemoteSubscriptionResourceUpdate resourceUpdate = new RemoteSubscriptionResourceUpdate(); resourceUpdate.setRemoteSubscriptionResource( rsr ); - resourceUpdate.setRemoteLastUpdated( rsr.getLastUpdatedAt() ); + resourceUpdate.setRemoteLastUpdated( LocalDateTime.now() ); rsr.setResourceUpdate( resourceUpdate ); autoCreatedRemoteSubscriptionResources.add( rsr ); diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomRuleRepositoryImpl.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomRuleRepositoryImpl.java index 9e2bc6a0..74311b79 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomRuleRepositoryImpl.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomRuleRepositoryImpl.java @@ -43,8 +43,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; /** * Implementation of {@link CustomRuleRepository}. @@ -68,15 +70,20 @@ public CustomRuleRepositoryImpl( @Nonnull EntityManager entityManager ) @Nonnull public List findAllByInputData( @Nonnull FhirResourceType fhirResourceType, @Nullable Collection systemCodeValues ) { - final List rules = new ArrayList<>( - entityManager.createNamedQuery( AbstractRule.FIND_RULES_BY_TYPE_NAMED_QUERY, AbstractRule.class ).setParameter( "fhirResourceType", fhirResourceType ).getResultList() ); - if ( (systemCodeValues != null) && !systemCodeValues.isEmpty() ) + final List rules; + if ( (systemCodeValues == null) || systemCodeValues.isEmpty() ) { - for ( final SystemCodeValue scv : new HashSet<>( systemCodeValues ) ) - { - rules.addAll( entityManager.createNamedQuery( AbstractRule.FIND_RULES_BY_TYPE_CODES_NAMED_QUERY, AbstractRule.class ).setParameter( "fhirResourceType", fhirResourceType ) - .setParameter( "system", scv.getSystem() ).setParameter( "code", scv.getCode() ).getResultList() ); - } + rules = new ArrayList<>( + entityManager.createNamedQuery( AbstractRule.FIND_RULES_BY_TYPE_NAMED_QUERY, AbstractRule.class ) + .setParameter( "fhirResourceType", fhirResourceType ).getResultList() ); + } + else + { + final Set systemCodes = systemCodeValues.stream().map( SystemCodeValue::toString ) + .collect( Collectors.toCollection( TreeSet::new ) ); + rules = new ArrayList<>( entityManager.createNamedQuery( AbstractRule.FIND_RULES_BY_TYPE_CODES_NAMED_QUERY, AbstractRule.class ) + .setParameter( "fhirResourceType", fhirResourceType ) + .setParameter( "systemCodeValues", systemCodes ).getResultList() ); } Collections.sort( rules ); diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/FhirAdapterMetadataEventListener.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/FhirAdapterMetadataEventListener.java index 6f16dc63..0369718f 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/FhirAdapterMetadataEventListener.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/FhirAdapterMetadataEventListener.java @@ -29,18 +29,13 @@ */ import org.dhis2.fhir.adapter.fhir.metadata.repository.FhirAdapterMetadata; +import org.dhis2.fhir.adapter.fhir.security.AdapterSecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.data.rest.core.event.AbstractRepositoryEventListener; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; -import javax.annotation.Nullable; - /** * Event listener that logs the creation, update and deletion of metadata as long * as the username that requested the action can be determined. @@ -56,7 +51,7 @@ public class FhirAdapterMetadataEventListener extends AbstractRepositoryEventLis @Override protected void onAfterCreate( FhirAdapterMetadata entity ) { - final String username = getCurrentUsername(); + final String username = AdapterSecurityUtils.getCurrentUsername(); if ( username != null ) { logger.info( "User {} created entity {} with ID {}.", username, entity.getClass().getSimpleName(), entity.getId() ); @@ -66,7 +61,7 @@ protected void onAfterCreate( FhirAdapterMetadata entity ) @Override protected void onAfterSave( FhirAdapterMetadata entity ) { - final String username = getCurrentUsername(); + final String username = AdapterSecurityUtils.getCurrentUsername(); if ( username != null ) { logger.info( "User {} saved entity {} with ID {}.", username, entity.getClass().getSimpleName(), entity.getId() ); @@ -76,7 +71,7 @@ protected void onAfterSave( FhirAdapterMetadata entity ) @Override protected void onAfterLinkSave( FhirAdapterMetadata parent, Object linked ) { - final String username = getCurrentUsername(); + final String username = AdapterSecurityUtils.getCurrentUsername(); if ( username != null ) { if ( linked instanceof FhirAdapterMetadata ) @@ -97,7 +92,7 @@ protected void onAfterLinkSave( FhirAdapterMetadata parent, Object linked ) @Override protected void onAfterLinkDelete( FhirAdapterMetadata parent, Object linked ) { - final String username = getCurrentUsername(); + final String username = AdapterSecurityUtils.getCurrentUsername(); if ( username != null ) { if ( linked instanceof FhirAdapterMetadata ) @@ -118,30 +113,10 @@ protected void onAfterLinkDelete( FhirAdapterMetadata parent, Object linked ) @Override protected void onAfterDelete( FhirAdapterMetadata entity ) { - final String username = getCurrentUsername(); + final String username = AdapterSecurityUtils.getCurrentUsername(); if ( username != null ) { logger.info( "User {} deleted entity {} with ID {}.", username, entity.getClass().getSimpleName(), entity.getId() ); } } - - @Nullable - public static String getCurrentUsername() - { - final SecurityContext context = SecurityContextHolder.getContext(); - if ( context == null ) - { - return null; - } - final Authentication authentication = context.getAuthentication(); - if ( authentication == null ) - { - return null; - } - if ( !(authentication.getPrincipal() instanceof UserDetails) ) - { - return null; - } - return ((UserDetails) authentication.getPrincipal()).getUsername(); - } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/VersionedBaseMetadataEventListener.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/VersionedBaseMetadataEventListener.java deleted file mode 100644 index 1818a7b3..00000000 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/VersionedBaseMetadataEventListener.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.dhis2.fhir.adapter.fhir.metadata.repository.listener; - -/* - * Copyright (c) 2004-2018, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import org.dhis2.fhir.adapter.fhir.metadata.model.VersionedBaseMetadata; -import org.springframework.core.annotation.Order; -import org.springframework.data.rest.core.event.AbstractRepositoryEventListener; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; - -/** - * Event listener that sets the metadata attributes in entity classes - * that extends {@linkplain VersionedBaseMetadata base metadata} class. Since - * {@link VersionedBaseMetadata#getCreatedAt()} is not updatable in database, - * value needs not to be reset. - * - * @author volsch - */ -@Component -@Order( value = 1 ) -public class VersionedBaseMetadataEventListener extends AbstractRepositoryEventListener -{ - @Override - protected void onBeforeCreate( VersionedBaseMetadata entity ) - { - entity.setCreatedAt( LocalDateTime.now() ); - entity.setLastUpdatedAt( entity.getCreatedAt() ); - entity.setLastUpdatedBy( FhirAdapterMetadataEventListener.getCurrentUsername() ); - } - - @Override - protected void onBeforeSave( VersionedBaseMetadata entity ) - { - entity.setLastUpdatedAt( LocalDateTime.now() ); - entity.setLastUpdatedBy( FhirAdapterMetadataEventListener.getCurrentUsername() ); - } - - @Override - protected void onBeforeLinkSave( VersionedBaseMetadata parent, Object linked ) - { - parent.setLastUpdatedAt( LocalDateTime.now() ); - parent.setLastUpdatedBy( FhirAdapterMetadataEventListener.getCurrentUsername() ); - } - - @Override - protected void onBeforeLinkDelete( VersionedBaseMetadata parent, Object linked ) - { - parent.setLastUpdatedAt( LocalDateTime.now() ); - parent.setLastUpdatedBy( FhirAdapterMetadataEventListener.getCurrentUsername() ); - } -} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveCodeValidator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveCodeValidator.java index b15281ec..60779cd0 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveCodeValidator.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveCodeValidator.java @@ -79,5 +79,17 @@ else if ( code.getCode().contains( "," ) ) { errors.rejectValue( "code", "Code.code.length", new Object[]{ Code.MAX_CODE_LENGTH }, "Code must not be longer than {0} characters." ); } + if ( StringUtils.isBlank( code.getMappedCode() ) ) + { + errors.rejectValue( "mappedCode", "Code.mappedCode.blank", "MappedCode must not be blank." ); + } + else if ( code.getMappedCode().contains( "," ) ) + { + errors.rejectValue( "mappedCode", "Code.mappedCode.comma", "MappedCode must not contain commas." ); + } + if ( StringUtils.length( code.getMappedCode() ) > Code.MAX_MAPPED_CODE_LENGTH ) + { + errors.rejectValue( "mappedCode", "Code.mappedCode.length", new Object[]{ Code.MAX_MAPPED_CODE_LENGTH }, "Mapped code must not be longer than {0} characters." ); + } } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveRemoteSubscriptionValidator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveRemoteSubscriptionValidator.java index ef7eb4dd..b9d5c62b 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveRemoteSubscriptionValidator.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveRemoteSubscriptionValidator.java @@ -92,9 +92,10 @@ public void validate( Object target, @Nonnull Errors errors ) } else { - if ( !ValidatorUtils.isValidUrl( remoteSubscription.getAdapterEndpoint().getBaseUrl() ) ) + final String baseUrlProtocol = ValidatorUtils.getUrlScheme( remoteSubscription.getAdapterEndpoint().getBaseUrl() ); + if ( isValidUrl( baseUrlProtocol ) ) { - errors.rejectValue( "baseUrl", "RemoteSubscription.adapterEndpoint.baseUrl.invalid", "Adapter endpoint base URL is not a valid URL." ); + errors.rejectValue( "baseUrl", "RemoteSubscription.adapterEndpoint.baseUrl.invalid", "Adapter endpoint base URL is not a valid HTTP/HTTPS URL." ); } if ( StringUtils.length( remoteSubscription.getAdapterEndpoint().getBaseUrl() ) > SubscriptionAdapterEndpoint.MAX_BASE_URL_LENGTH ) { @@ -152,9 +153,10 @@ public void validate( Object target, @Nonnull Errors errors ) } else { - if ( !ValidatorUtils.isValidUrl( remoteSubscription.getFhirEndpoint().getBaseUrl() ) ) + final String baseUrlProtocol = ValidatorUtils.getUrlScheme( remoteSubscription.getFhirEndpoint().getBaseUrl() ); + if ( isValidUrl( baseUrlProtocol ) ) { - errors.rejectValue( "baseUrl", "RemoteSubscription.fhirEndpoint.baseUrl.blank", "FHIR endpoint base URL is not a valid URL." ); + errors.rejectValue( "baseUrl", "RemoteSubscription.fhirEndpoint.baseUrl.blank", "FHIR endpoint base URL is not a valid HTTP/HTTPS URL." ); } if ( StringUtils.length( remoteSubscription.getAdapterEndpoint().getBaseUrl() ) > SubscriptionFhirEndpoint.MAX_BASE_URL_LENGTH ) { @@ -186,4 +188,9 @@ public void validate( Object target, @Nonnull Errors errors ) } errors.popNestedPath(); } + + private boolean isValidUrl( String baseUrlProtocol ) + { + return (baseUrlProtocol == null) || (!"http".equals( baseUrlProtocol ) && !"https".equals( baseUrlProtocol )); + } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveScriptSourceValidator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveScriptSourceValidator.java index 0e335646..531a7de7 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveScriptSourceValidator.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveScriptSourceValidator.java @@ -30,6 +30,8 @@ import org.apache.commons.lang3.StringUtils; import org.dhis2.fhir.adapter.fhir.metadata.model.ScriptSource; +import org.dhis2.fhir.adapter.script.ScriptCompilationException; +import org.dhis2.fhir.adapter.script.ScriptCompiler; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -44,6 +46,13 @@ @Component public class BeforeCreateSaveScriptSourceValidator implements Validator { + private final ScriptCompiler scriptCompiler; + + public BeforeCreateSaveScriptSourceValidator( @Nonnull ScriptCompiler scriptCompiler ) + { + this.scriptCompiler = scriptCompiler; + } + @Override public boolean supports( @Nonnull Class clazz ) { @@ -63,6 +72,17 @@ public void validate( Object target, @Nonnull Errors errors ) { errors.rejectValue( "sourceText", "ScriptSource.sourceText.blank", "Source text must not be blank." ); } + else + { + try + { + scriptCompiler.compile( scriptSource.getSourceText() ); + } + catch ( ScriptCompilationException e ) + { + errors.rejectValue( "sourceText", "ScriptSource.sourceText.error", e.getMessage() ); + } + } if ( scriptSource.getSourceType() == null ) { errors.rejectValue( "sourceType", "ScriptSource.sourceType.null", "Source type is mandatory." ); diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveSystemValidator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveSystemValidator.java index 66e67c5e..67636800 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveSystemValidator.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveSystemValidator.java @@ -71,5 +71,13 @@ public void validate( Object target, @Nonnull Errors errors ) { errors.rejectValue( "code", "System.code.length", new Object[]{ System.MAX_CODE_LENGTH }, "Code must not be longer than {0} characters." ); } + if ( StringUtils.isBlank( system.getSystemUri() ) ) + { + errors.rejectValue( "systemUri", "System.systemUri.blank", "System URI must not be blank." ); + } + if ( StringUtils.length( system.getSystemUri() ) > System.MAX_SYSTEM_URI_LENGTH ) + { + errors.rejectValue( "systemUri", "System.systemUri.length", new Object[]{ System.MAX_SYSTEM_URI_LENGTH }, "System URI must not be longer than {0} characters." ); + } } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/ValidatorUtils.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/ValidatorUtils.java index 3dd2925c..ec6c7ab3 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/ValidatorUtils.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/ValidatorUtils.java @@ -39,21 +39,20 @@ */ public abstract class ValidatorUtils { - public static boolean isValidUrl( @Nullable String url ) + public static String getUrlScheme( @Nullable String url ) { if ( url == null ) { - return false; + return null; } try { - new URL( url ); + return new URL( url ).getProtocol(); } catch ( MalformedURLException e ) { - return false; + return null; } - return true; } private ValidatorUtils() diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/model/SystemCodeValue.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/model/SystemCodeValue.java index f9510397..720232f0 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/model/SystemCodeValue.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/model/SystemCodeValue.java @@ -35,6 +35,8 @@ public class SystemCodeValue implements Serializable { private static final long serialVersionUID = -4751270623619799949L; + public static final String SEPARATOR = "|"; + private final String system; private final String code; @@ -74,6 +76,6 @@ public int hashCode() @Override public String toString() { - return (system == null) ? code : (system + "|" + code); + return (system == null) ? code : (system + SEPARATOR + code); } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/remote/impl/RemoteConfig.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/remote/impl/RemoteConfig.java index 75ceb9a3..09d33be1 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/remote/impl/RemoteConfig.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/remote/impl/RemoteConfig.java @@ -30,6 +30,8 @@ import org.dhis2.fhir.adapter.queue.QueueConfig; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.context.annotation.Configuration; import org.springframework.validation.annotation.Validated; @@ -44,6 +46,7 @@ * @author volsch */ @Configuration( "fhirRemoteConfig" ) +@EnableConfigurationProperties @ConfigurationProperties( "dhis2.fhir-adapter.remote" ) @Validated public class RemoteConfig implements Serializable @@ -51,6 +54,7 @@ public class RemoteConfig implements Serializable private static final long serialVersionUID = -2505291312383786207L; @NotNull + @NestedConfigurationProperty @Valid private QueueConfig webHookRequestQueue = new QueueConfig(); diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/remote/impl/RemoteProcessorConfig.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/remote/impl/RemoteProcessorConfig.java index a0eb7a11..484f2e46 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/remote/impl/RemoteProcessorConfig.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/remote/impl/RemoteProcessorConfig.java @@ -29,6 +29,7 @@ */ import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.validation.annotation.Validated; @@ -36,6 +37,7 @@ import java.io.Serializable; @Configuration +@EnableConfigurationProperties @ConfigurationProperties( "dhis2.fhir-adapter.remote.processor" ) @Validated public class RemoteProcessorConfig implements Serializable diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirRepositoryImpl.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirRepositoryImpl.java index 2794fe3c..c48e5acd 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirRepositoryImpl.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirRepositoryImpl.java @@ -327,7 +327,7 @@ protected boolean saveInternally( @Nonnull RemoteSubscriptionResource subscripti fhirRequest.setVersion( subscriptionResource.getRemoteSubscription().getFhirVersion() ); fhirRequest.setParameters( ArrayListMultimap.create() ); fhirRequest.setResourceSystemsByType( systems.stream() - .map( s -> new ResourceSystem( s.getFhirResourceType(), s.getSystem().getSystemUri() ) ) + .map( s -> new ResourceSystem( s.getFhirResourceType(), s.getSystem().getSystemUri(), s.getCodePrefix() ) ) .collect( Collectors.toMap( ResourceSystem::getFhirResourceType, rs -> rs ) ) ); final FhirToDhisTransformOutcome outcome; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceCacheConfig.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceCacheConfig.java index 0cd24d91..6ac9c28f 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceCacheConfig.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceCacheConfig.java @@ -31,6 +31,7 @@ import org.dhis2.fhir.adapter.cache.AbstractSimpleCacheConfig; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -41,8 +42,11 @@ /** * Cache configuration for FHIR Resources. + * + * @author volsch */ @Configuration +@EnableConfigurationProperties @ConfigurationProperties( "dhis2.fhir-adapter.cache.fhir" ) @Validated public class FhirResourceCacheConfig extends AbstractSimpleCacheConfig diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/RepositoryConfig.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/RepositoryConfig.java index 0af91762..2e8b5758 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/RepositoryConfig.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/RepositoryConfig.java @@ -30,6 +30,8 @@ import org.dhis2.fhir.adapter.queue.QueueConfig; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.context.annotation.Configuration; import org.springframework.validation.annotation.Validated; @@ -44,6 +46,7 @@ * @author volsch */ @Configuration( "fhirRepositoryConfig" ) +@EnableConfigurationProperties @ConfigurationProperties( "dhis2.fhir-adapter.repository" ) @Validated public class RepositoryConfig implements Serializable @@ -51,10 +54,12 @@ public class RepositoryConfig implements Serializable private static final long serialVersionUID = -2505291312383786207L; @NotNull + @NestedConfigurationProperty @Valid private QueueConfig fhirResourceQueue = new QueueConfig(); @NotNull + @NestedConfigurationProperty @Valid private QueueConfig fhirResourceDlQueue = new QueueConfig(); diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/security/AdapterSecurityConfig.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/security/AdapterSecurityConfig.java index 1c158d5d..711e4cb4 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/security/AdapterSecurityConfig.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/security/AdapterSecurityConfig.java @@ -33,6 +33,7 @@ import com.google.common.collect.Multimap; import org.dhis2.fhir.adapter.dhis.security.SecurityConfig; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import javax.annotation.Nonnull; @@ -47,6 +48,7 @@ * @author volsch */ @Configuration +@EnableConfigurationProperties @ConfigurationProperties( "dhis2.fhir-adapter.security.authorities" ) public class AdapterSecurityConfig implements SecurityConfig { @@ -56,7 +58,7 @@ public class AdapterSecurityConfig implements SecurityConfig private Set dataMapping = new HashSet<>(); - private Multimap authoritiesMappings; + private transient Multimap authoritiesMappings; @Nonnull public Set getAdministration() diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/security/AdapterSecurityUtils.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/security/AdapterSecurityUtils.java new file mode 100644 index 00000000..568f9339 --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/security/AdapterSecurityUtils.java @@ -0,0 +1,69 @@ +package org.dhis2.fhir.adapter.fhir.security; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.annotation.Nullable; + +/** + * Security utilities that are used by the adapter. + * + * @author volsch + */ +public abstract class AdapterSecurityUtils +{ + @Nullable + public static String getCurrentUsername() + { + final SecurityContext context = SecurityContextHolder.getContext(); + if ( context == null ) + { + return null; + } + final Authentication authentication = context.getAuthentication(); + if ( authentication == null ) + { + return null; + } + if ( !(authentication.getPrincipal() instanceof UserDetails) ) + { + return null; + } + return ((UserDetails) authentication.getPrincipal()).getUsername(); + } + + private AdapterSecurityUtils() + { + super(); + } +} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/TransformationConfig.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/TransformationConfig.java index b4ebd6c5..77c252f6 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/TransformationConfig.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/TransformationConfig.java @@ -30,17 +30,22 @@ import org.dhis2.fhir.adapter.fhir.script.ScriptExecutionContext; import org.dhis2.fhir.adapter.fhir.script.impl.ThreadLocalScriptExecutionContext; +import org.dhis2.fhir.adapter.script.ScriptCompiler; +import org.dhis2.fhir.adapter.script.impl.ScriptCompilerImpl; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scripting.ScriptEvaluator; import org.springframework.scripting.support.StandardScriptEvaluator; import org.springframework.validation.annotation.Validated; +import javax.annotation.Nonnull; import javax.validation.constraints.NotBlank; import java.io.Serializable; @Configuration +@EnableConfigurationProperties @ConfigurationProperties( "dhis2.fhir-adapter.transformation" ) @Validated public class TransformationConfig implements Serializable @@ -61,16 +66,25 @@ public void setScriptEngineName( String scriptEngineName ) } @Bean + @Nonnull protected ScriptExecutionContext scriptExecutionContext() { return new ThreadLocalScriptExecutionContext(); } @Bean + @Nonnull protected ScriptEvaluator scriptEvaluator() { final StandardScriptEvaluator scriptEvaluator = new StandardScriptEvaluator(); scriptEvaluator.setEngineName( getScriptEngineName() ); return scriptEvaluator; } + + @Bean + @Nonnull + protected ScriptCompiler scriptCompiler() + { + return new ScriptCompilerImpl( getScriptEngineName() ); + } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractCodeFhirToDhisTransformerUtils.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractCodeFhirToDhisTransformerUtils.java index 8e3f988f..457e0d08 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractCodeFhirToDhisTransformerUtils.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractCodeFhirToDhisTransformerUtils.java @@ -29,10 +29,18 @@ */ import org.dhis2.fhir.adapter.Scriptable; +import org.dhis2.fhir.adapter.fhir.metadata.model.Code; +import org.dhis2.fhir.adapter.fhir.metadata.model.FhirResourceType; +import org.dhis2.fhir.adapter.fhir.metadata.model.ScriptVariable; +import org.dhis2.fhir.adapter.fhir.metadata.repository.CodeRepository; import org.dhis2.fhir.adapter.fhir.model.SystemCodeValue; import org.dhis2.fhir.adapter.fhir.script.ScriptExecutionContext; +import org.dhis2.fhir.adapter.fhir.script.ScriptExecutionRequired; +import org.dhis2.fhir.adapter.fhir.transform.FhirToDhisTransformerContext; import org.dhis2.fhir.adapter.fhir.transform.TransformerException; import org.dhis2.fhir.adapter.fhir.transform.TransformerMappingException; +import org.dhis2.fhir.adapter.fhir.transform.impl.TransformerScriptException; +import org.dhis2.fhir.adapter.fhir.transform.model.ResourceSystem; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.ICompositeType; import org.hl7.fhir.instance.model.api.IDomainResource; @@ -54,11 +62,14 @@ public abstract class AbstractCodeFhirToDhisTransformerUtils extends AbstractFhi private static final List GET_CODE_METHOD_NAMES = Collections.unmodifiableList( Arrays.asList( "getCode", "getVaccineCode" ) ); + private final CodeRepository codeRepository; + private volatile Map, Method> codeMethods = new HashMap<>(); - protected AbstractCodeFhirToDhisTransformerUtils( @Nonnull ScriptExecutionContext scriptExecutionContext ) + protected AbstractCodeFhirToDhisTransformerUtils( @Nonnull ScriptExecutionContext scriptExecutionContext, @Nonnull CodeRepository codeRepository ) { super( scriptExecutionContext ); + this.codeRepository = codeRepository; } @Nonnull @@ -102,6 +113,36 @@ public List getResourceCodes( @Nullable IBaseResource baseResou return null; } + @ScriptExecutionRequired + public String getMappedCode( @Nullable String code, @Nonnull String fhirResourceType ) + { + if ( code == null ) + { + return null; + } + final FhirResourceType resourceType; + try + { + resourceType = FhirResourceType.valueOf( fhirResourceType.toString() ); + } + catch ( IllegalArgumentException e ) + { + throw new TransformerScriptException( "Invalid FHIR resource type: " + fhirResourceType, e ); + } + + final FhirToDhisTransformerContext context = getScriptVariable( ScriptVariable.CONTEXT.getVariableName(), FhirToDhisTransformerContext.class ); + final ResourceSystem resourceSystem = context.getFhirRequest().getOptionalResourceSystem( resourceType ) + .orElseThrow( () -> new TransformerMappingException( "No system has been defined for resource type " + resourceType + "." ) ); + + final Code c = codeRepository.findBySystemCode( resourceSystem.getSystem(), code ).stream().findFirst().orElse( null ); + if ( c == null ) + { + return null; + } + + return (c.getMappedCode() == null) ? c.getCode() : c.getMappedCode(); + } + @Nullable private Method getCodeMethod( @Nonnull IDomainResource domainResource ) { diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractFhirClientTransformUtils.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractFhirClientTransformUtils.java index 0f66fe93..0546ffa3 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractFhirClientTransformUtils.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractFhirClientTransformUtils.java @@ -122,12 +122,12 @@ protected IBaseResource queryLatestPrioritizedByMappedCodes( @Nonnull String res } final Collection systemCodes = systemCodeRepository.findAllByCodes( convertedMappedCodes ); final Map systemCodeValueIndexes = systemCodes.stream().collect( - Collectors.toMap( SystemCode::getSystemCodeValue, sc -> convertedMappedCodes.indexOf( sc.getCode().getCode() ) ) ); + Collectors.toMap( SystemCode::getCalculatedSystemCodeValue, sc -> convertedMappedCodes.indexOf( sc.getCode().getCode() ) ) ); final int resultingMaxCount = (maxCount == null) ? systemCodes.size() : maxCount; final Map> filterMap = createFilter( referencedResourceParameter, referencedResourceType, referencedResourceId ); filterMap.put( codeParameter, Collections.singletonList( - String.join( ",", systemCodes.stream().map( sc -> sc.getSystemCodeValue().toString() ).collect( Collectors.toList() ) ) ) ); + String.join( ",", systemCodes.stream().map( sc -> sc.getCalculatedSystemCodeValue().toString() ).collect( Collectors.toList() ) ) ) ); int processedResources = 0; int foundMinIndex = Integer.MAX_VALUE; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractIdentifierFhirToDhisTransformerUtils.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractIdentifierFhirToDhisTransformerUtils.java index 53ec16c1..349dea11 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractIdentifierFhirToDhisTransformerUtils.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/AbstractIdentifierFhirToDhisTransformerUtils.java @@ -78,7 +78,7 @@ public final String getScriptAttrName() @ScriptExecutionRequired public String getReferenceIdentifier( @Nullable IBaseReference reference, @Nonnull Object fhirResourceType ) throws TransformerException { - if ( reference == null ) + if ( (reference == null) || reference.isEmpty() ) { return null; } @@ -94,7 +94,7 @@ public String getReferenceIdentifier( @Nullable IBaseReference reference, @Nonnu @Nullable public String getReferenceIdentifier( @Nullable IBaseReference reference, @Nonnull Object fhirResourceType, @Nullable String system ) throws TransformerException { - if ( reference == null ) + if ( (reference == null) || reference.isEmpty() ) { return null; } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/OrganizationUnitTransformerUtils.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/OrganizationUnitTransformerUtils.java new file mode 100644 index 00000000..62dc3543 --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/impl/util/OrganizationUnitTransformerUtils.java @@ -0,0 +1,106 @@ +package org.dhis2.fhir.adapter.fhir.transform.impl.util; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.apache.commons.lang.StringUtils; +import org.dhis2.fhir.adapter.dhis.model.Reference; +import org.dhis2.fhir.adapter.dhis.model.ReferenceType; +import org.dhis2.fhir.adapter.dhis.orgunit.OrganisationUnitService; +import org.dhis2.fhir.adapter.fhir.metadata.model.FhirResourceType; +import org.dhis2.fhir.adapter.fhir.metadata.model.ScriptVariable; +import org.dhis2.fhir.adapter.fhir.model.FhirVersion; +import org.dhis2.fhir.adapter.fhir.script.ScriptExecutionContext; +import org.dhis2.fhir.adapter.fhir.script.ScriptExecutionRequired; +import org.dhis2.fhir.adapter.fhir.transform.FhirToDhisTransformerContext; +import org.dhis2.fhir.adapter.fhir.transform.TransformerMappingException; +import org.dhis2.fhir.adapter.fhir.transform.model.ResourceSystem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Set; + +/** + * Transformer utilities for organization units. + * + * @author volsch + */ +@Component +public class OrganizationUnitTransformerUtils extends AbstractFhirToDhisTransformerUtils +{ + private static final String SCRIPT_ATTR_NAME = "organizationUnitUtils"; + + private final Logger logger = LoggerFactory.getLogger( getClass() ); + + private final OrganisationUnitService organisationUnitService; + + public OrganizationUnitTransformerUtils( @Nonnull ScriptExecutionContext scriptExecutionContext, + @Nonnull OrganisationUnitService organisationUnitService ) + { + super( scriptExecutionContext ); + this.organisationUnitService = organisationUnitService; + } + + @Nonnull + @Override + public Set getFhirVersions() + { + return FhirVersion.ALL; + } + + @Nonnull + @Override + public String getScriptAttrName() + { + return SCRIPT_ATTR_NAME; + } + + @ScriptExecutionRequired + @Nullable + public String existsWithPrefix( @Nullable String code ) + { + if ( code == null ) + { + return null; + } + + final FhirToDhisTransformerContext context = getScriptVariable( ScriptVariable.CONTEXT.getVariableName(), FhirToDhisTransformerContext.class ); + final ResourceSystem resourceSystem = context.getFhirRequest().getOptionalResourceSystem( FhirResourceType.ORGANIZATION ) + .orElseThrow( () -> new TransformerMappingException( "No system has been defined for resource type " + FhirResourceType.ORGANIZATION + "." ) ); + + final String resultingCode = StringUtils.defaultString( resourceSystem.getCodePrefix() ) + code; + if ( organisationUnitService.findOneByReference( new Reference( resultingCode, ReferenceType.CODE ) ).isPresent() ) + { + return resultingCode; + } + return null; + } +} \ No newline at end of file diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/model/ResourceSystem.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/model/ResourceSystem.java index 14298fff..a2d64525 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/model/ResourceSystem.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/model/ResourceSystem.java @@ -31,6 +31,7 @@ import org.dhis2.fhir.adapter.fhir.metadata.model.FhirResourceType; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.Serializable; import java.util.Objects; @@ -45,10 +46,13 @@ public class ResourceSystem implements Serializable private final String system; - public ResourceSystem( @Nonnull FhirResourceType fhirResourceType, @Nonnull String system ) + private final String codePrefix; + + public ResourceSystem( @Nonnull FhirResourceType fhirResourceType, @Nonnull String system, @Nullable String codePrefix ) { this.fhirResourceType = fhirResourceType; this.system = system; + this.codePrefix = codePrefix; } @Nonnull @@ -63,6 +67,12 @@ public String getSystem() return system; } + @Nullable + public String getCodePrefix() + { + return codePrefix; + } + @Override public boolean equals( Object o ) { diff --git a/fhir/src/main/resources/db/migration/production/V1.0.0.0_0_1__Initial.sql b/fhir/src/main/resources/db/migration/production/V1.0.0.0_0_1__Initial.sql index 2c23657d..d4e1644a 100644 --- a/fhir/src/main/resources/db/migration/production/V1.0.0.0_0_1__Initial.sql +++ b/fhir/src/main/resources/db/migration/production/V1.0.0.0_0_1__Initial.sql @@ -182,8 +182,8 @@ CREATE TABLE fhir_constant ( data_type VARCHAR(30) NOT NULL, value VARCHAR(250), CONSTRAINT fhir_constant_pk PRIMARY KEY (id), - CONSTRAINT fhir_constant_uk1 UNIQUE (name), - CONSTRAINT fhir_constant_uk2 UNIQUE (code), + CONSTRAINT fhir_constant_uk_name UNIQUE (name), + CONSTRAINT fhir_constant_uk_code UNIQUE (code), CONSTRAINT fhir_constant_fk1 FOREIGN KEY(data_type) REFERENCES fhir_data_type_enum(value) ); COMMENT ON TABLE fhir_constant IS 'Contains mappings between a code and a value. The code may be GENDER_FEMALE and the value may be the option set value for female that has been configured in DHIS2. This can be used to map between the FHIR enumerations and frequently used DHIS2 option set values.'; @@ -325,8 +325,8 @@ CREATE TABLE fhir_executable_script ( code VARCHAR(100) NOT NULL, description TEXT, CONSTRAINT fhir_executable_script_pk PRIMARY KEY (id), - CONSTRAINT fhir_executable_script_uk1 UNIQUE (name), - CONSTRAINT fhir_executable_script_uk2 UNIQUE (code), + CONSTRAINT fhir_executable_script_uk_name UNIQUE (name), + CONSTRAINT fhir_executable_script_uk_code UNIQUE (code), CONSTRAINT fhir_executable_script_fk1 FOREIGN KEY (script_id) REFERENCES fhir_script (id) ); CREATE INDEX fhir_executable_script_i1 @@ -374,8 +374,8 @@ CREATE TABLE fhir_code_category ( code VARCHAR(50) NOT NULL, description TEXT, CONSTRAINT fhir_code_category_pk PRIMARY KEY (id), - CONSTRAINT fhir_code_category_uk1 UNIQUE (name), - CONSTRAINT fhir_code_category_uk2 UNIQUE (code) + CONSTRAINT fhir_code_category_uk_name UNIQUE (name), + CONSTRAINT fhir_code_category_uk_code UNIQUE (code) ); COMMENT ON TABLE fhir_code_category IS 'Contains a category for defined codes. The category is only required for grouping codes in order to finding and using them manually.'; COMMENT ON COLUMN fhir_code_category.id IS 'Unique ID of entity.'; @@ -396,14 +396,17 @@ CREATE TABLE fhir_code ( code_category_id UUID NOT NULL, name VARCHAR(230) NOT NULL, code VARCHAR(50) NOT NULL, + mapped_code VARCHAR(50), description TEXT, CONSTRAINT fhir_code_pk PRIMARY KEY (id), CONSTRAINT fhir_code_fk1 FOREIGN KEY (code_category_id) REFERENCES fhir_code_category (id), - CONSTRAINT fhir_code_uk1 UNIQUE (name), - CONSTRAINT fhir_code_uk2 UNIQUE (code) + CONSTRAINT fhir_code_uk_name UNIQUE (name), + CONSTRAINT fhir_code_uk_code UNIQUE (code) ); CREATE INDEX fhir_code_i1 ON fhir_code (code_category_id); +CREATE INDEX fhir_code_i2 + ON fhir_code (mapped_code); COMMENT ON TABLE fhir_code IS 'Contains unique codes that can be used by rules, transformations and scripts to reference system dependent codes (e.g. vaccine CVX codes).'; COMMENT ON COLUMN fhir_code.id IS 'Unique ID of entity.'; COMMENT ON COLUMN fhir_code.version IS 'The version of the entity used for optimistic locking. When changing the entity this value must be incremented.'; @@ -413,6 +416,7 @@ COMMENT ON COLUMN fhir_code.last_updated_at IS 'The timestamp when the entity ha COMMENT ON COLUMN fhir_code.code_category_id IS 'References the code category to which the code belongs to.'; COMMENT ON COLUMN fhir_code.name IS 'The unique name of the code.'; COMMENT ON COLUMN fhir_code.code IS 'The unique code that can be used by rules, transformations and scripts.'; +COMMENT ON COLUMN fhir_code.mapped_code IS 'The optional mapped code (unless it is the same like the code) that is used by DHIS2 (e.g. DHIS2 organization unit code).'; COMMENT ON COLUMN fhir_code.description IS 'The detailed description that describes for which purpose the code is used. This may also contain details about the license that affects the code. In such a case the description must not be changed.'; CREATE TABLE fhir_code_set ( @@ -427,8 +431,8 @@ CREATE TABLE fhir_code_set ( description TEXT, CONSTRAINT fhir_code_set_pk PRIMARY KEY (id), CONSTRAINT fhir_code_set_fk1 FOREIGN KEY (code_category_id) REFERENCES fhir_code_category (id), - CONSTRAINT fhir_code_set_uk1 UNIQUE (name), - CONSTRAINT fhir_code_set_uk2 UNIQUE (code) + CONSTRAINT fhir_code_set_uk_name UNIQUE (name), + CONSTRAINT fhir_code_set_uk_code UNIQUE (code) ); CREATE INDEX fhir_code_set_i1 ON fhir_code_set (code_category_id); @@ -466,14 +470,14 @@ CREATE TABLE fhir_system ( last_updated_at TIMESTAMP(3) WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, name VARCHAR(230) NOT NULL, code VARCHAR(50) NOT NULL, - system_uri VARCHAR(140) NOT NULL, + system_uri VARCHAR(120) NOT NULL, enabled BOOLEAN NOT NULL DEFAULT TRUE, description TEXT, description_protected BOOLEAN NOT NULL DEFAULT FALSE, CONSTRAINT fhir_system_pk PRIMARY KEY (id), - CONSTRAINT fhir_system_uk1 UNIQUE (name), - CONSTRAINT fhir_system_uk2 UNIQUE (code), - CONSTRAINT fhir_system_uk3 UNIQUE (system_uri) + CONSTRAINT fhir_system_uk_name UNIQUE (name), + CONSTRAINT fhir_system_uk_code UNIQUE (code), + CONSTRAINT fhir_system_uk_uri UNIQUE (system_uri) ); COMMENT ON TABLE fhir_system IS 'Contains the definition of all system URIs (e.g. system URI of vaccine CVS codes or also system URI for identifiers of patients) that are used by FHIR Resources.'; COMMENT ON COLUMN fhir_system.id IS 'Unique ID of entity.'; @@ -489,20 +493,21 @@ COMMENT ON COLUMN fhir_system.description_protected IS 'Defines if the descripti COMMENT ON COLUMN fhir_system.system_uri IS 'The system URI (e.g. http://hl7.org/fhir/sid/cvx) the system that is defined by this entity.'; CREATE TABLE fhir_system_code ( - id UUID NOT NULL DEFAULT UUID_GENERATE_V4(), - version BIGINT NOT NULL, - created_at TIMESTAMP(3) WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, - last_updated_by VARCHAR(11), - last_updated_at TIMESTAMP(3) WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, - system_id UUID NOT NULL, - code_id UUID NOT NULL, - system_code VARCHAR(120) NOT NULL, + id UUID NOT NULL DEFAULT UUID_GENERATE_V4(), + version BIGINT NOT NULL, + created_at TIMESTAMP(3) WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + last_updated_by VARCHAR(11), + last_updated_at TIMESTAMP(3) WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + system_id UUID NOT NULL, + code_id UUID NOT NULL, + system_code VARCHAR(120) NOT NULL, + system_code_value VARCHAR(241), CONSTRAINT fhir_system_code_pk PRIMARY KEY (id), CONSTRAINT fhir_system_code_fk1 FOREIGN KEY (system_id) REFERENCES fhir_system (id), CONSTRAINT fhir_system_code_fk2 FOREIGN KEY (code_id) REFERENCES fhir_code (id) ON DELETE CASCADE ); CREATE INDEX fhir_system_code_i1 - ON fhir_system_code (system_id); + ON fhir_system_code (system_id, system_code); CREATE INDEX fhir_system_code_i2 ON fhir_system_code (code_id); COMMENT ON TABLE fhir_system_code IS 'Contains the mapping between a code and a system specific code (e.g. the internal code for a vaccine that is used by the adapter to the national vaccine code that is used by a specific country).'; @@ -514,6 +519,7 @@ COMMENT ON COLUMN fhir_system_code.last_updated_at IS 'The timestamp when the en COMMENT ON COLUMN fhir_system_code.system_id IS 'References the system to which this code belongs to.'; COMMENT ON COLUMN fhir_system_code.system_code IS 'The system specific code (e.g. a CVX vaccine code or country or region specific vaccine code).'; COMMENT ON COLUMN fhir_system_code.code_id IS 'References the adapter internal code that is used by rules, transformations, mappings and in scripts.'; +COMMENT ON COLUMN fhir_system_code.system_code_value IS 'Combination of system URI and code separated by a pipe character. This is mainly used for search purpose.'; CREATE TABLE fhir_rule ( id UUID NOT NULL DEFAULT UUID_GENERATE_V4(), @@ -531,7 +537,7 @@ CREATE TABLE fhir_rule ( applicable_code_set_id UUID, transform_in_script_id UUID NOT NULL, CONSTRAINT fhir_rule_pk PRIMARY KEY (id), - CONSTRAINT fhir_rule_uk1 UNIQUE (name), + CONSTRAINT fhir_rule_uk_name UNIQUE (name), CONSTRAINT fhir_rule_fk1 FOREIGN KEY (applicable_in_script_id) REFERENCES fhir_executable_script (id), CONSTRAINT fhir_rule_fk2 FOREIGN KEY (applicable_code_set_id) REFERENCES fhir_code_set (id), CONSTRAINT fhir_rule_fk3 FOREIGN KEY (transform_in_script_id) REFERENCES fhir_executable_script (id), @@ -602,8 +608,8 @@ CREATE TABLE fhir_tracker_program ( before_script_id UUID, after_script_id UUID, CONSTRAINT fhir_tracker_program_pk PRIMARY KEY (id), - CONSTRAINT fhir_tracker_program_uk1 UNIQUE (name), - CONSTRAINT fhir_tracker_program_uk2 UNIQUE (program_ref), + CONSTRAINT fhir_tracker_program_uk_name UNIQUE (name), + CONSTRAINT fhir_tracker_program_uk_program UNIQUE (program_ref), CONSTRAINT fhir_tracker_program_fk1 FOREIGN KEY (tracked_entity_rule_id) REFERENCES fhir_tracked_entity_rule(id), CONSTRAINT fhir_tracker_program_fk2 FOREIGN KEY (creation_applicable_script_id) REFERENCES fhir_executable_script(id), CONSTRAINT fhir_tracker_program_fk3 FOREIGN KEY (creation_script_id) REFERENCES fhir_executable_script(id), @@ -661,8 +667,8 @@ CREATE TABLE fhir_tracker_program_stage ( after_period_day_type VARCHAR(20), after_period_days INTEGER NOT NULL DEFAULT 0, CONSTRAINT fhir_tracker_program_stage_pk PRIMARY KEY (id), - CONSTRAINT fhir_tracker_program_stage_uk1 UNIQUE (name), - CONSTRAINT fhir_tracker_program_stage_uk2 UNIQUE (program_id, program_stage_ref), + CONSTRAINT fhir_tracker_program_stage_uk_name UNIQUE (name), + CONSTRAINT fhir_tracker_program_stage_uk_program_stage UNIQUE (program_id, program_stage_ref), CONSTRAINT fhir_tracker_program_stage_fk1 FOREIGN KEY (program_id) REFERENCES fhir_tracker_program(id), CONSTRAINT fhir_tracker_program_stage_fk2 FOREIGN KEY (creation_applicable_script_id) REFERENCES fhir_executable_script(id), CONSTRAINT fhir_tracker_program_stage_fk3 FOREIGN KEY (creation_script_id) REFERENCES fhir_executable_script(id), @@ -774,7 +780,7 @@ CREATE TABLE fhir_resource_mapping ( event_loc_lookup_script_id UUID, effective_date_lookup_script_id UUID, CONSTRAINT fhir_resource_mapping_pk PRIMARY KEY (id), - CONSTRAINT fhir_resource_mapping_uk1 UNIQUE (fhir_resource_type), + CONSTRAINT fhir_resource_mapping_uk_fhir UNIQUE (fhir_resource_type), CONSTRAINT fhir_resource_mapping_fk1 FOREIGN KEY (tei_lookup_script_id) REFERENCES fhir_executable_script (id), CONSTRAINT fhir_resource_mapping_fk2 FOREIGN KEY (enrollment_org_lookup_script_id) REFERENCES fhir_executable_script (id), CONSTRAINT fhir_resource_mapping_fk3 FOREIGN KEY (event_org_lookup_script_id) REFERENCES fhir_executable_script (id), @@ -840,8 +846,8 @@ CREATE TABLE fhir_remote_subscription ( subscription_type VARCHAR(30) NOT NULL, auto_configuration BOOLEAN NOT NULL DEFAULT FALSE, CONSTRAINT fhir_remote_subscription_pk PRIMARY KEY (id), - CONSTRAINT fhir_remote_subscription_uk1 UNIQUE (name), - CONSTRAINT fhir_remote_subscription_uk2 UNIQUE (code), + CONSTRAINT fhir_remote_subscription_uk_name UNIQUE (name), + CONSTRAINT fhir_remote_subscription_uk_code UNIQUE (code), CONSTRAINT fhir_remote_subscription_fk1 FOREIGN KEY (fhir_version) REFERENCES fhir_version_enum(value), CONSTRAINT fhir_remote_subscription_fk2 FOREIGN KEY (dhis_authentication_method) REFERENCES fhir_authentication_method_enum(value), CONSTRAINT fhir_remote_subscription_fk3 FOREIGN KEY (subscription_type) REFERENCES subscription_type_enum(value) @@ -895,7 +901,7 @@ CREATE TABLE fhir_remote_subscription_system ( system_id UUID NOT NULL, code_prefix VARCHAR(20), CONSTRAINT fhir_remote_subscription_system_pk PRIMARY KEY (id), - CONSTRAINT fhir_remote_subscription_system_uk1 UNIQUE (remote_subscription_id , fhir_resource_type ), + CONSTRAINT fhir_remote_subscription_system_uk_fhir UNIQUE (remote_subscription_id , fhir_resource_type ), CONSTRAINT fhir_remote_subscription_system_fk1 FOREIGN KEY (remote_subscription_id) REFERENCES fhir_remote_subscription (id) ON DELETE CASCADE, CONSTRAINT fhir_remote_subscription_system_fk2 FOREIGN KEY (system_id) REFERENCES fhir_system (id), CONSTRAINT fhir_remote_subscription_system_fk3 FOREIGN KEY (fhir_resource_type) REFERENCES fhir_resource_type_enum (value) @@ -1014,6 +1020,8 @@ INSERT INTO fhir_system (id, version, name, code, system_uri, description) VALUES ('02d5719e-8859-445a-a815-5980d418b4e6', 0, 'RxNorm', 'SYSTEM_RXNORM', ' http://www.nlm.nih.gov/research/umls/rxnorm', 'RxNorm is made available by the US National Library of Medicine at http://www.nlm.nih.gov/research/umls/rxnorm.'); +INSERT INTO fhir_code_category (id, version, name, code, description) +VALUES ('9e1c64d8-82df-40e5-927b-a34c115f14e9', 0, 'Organization Unit', 'ORGANIZATION_UNIT', 'Organization units that exists on DHIS2.'); INSERT INTO fhir_code_category (id, version, name, code, description) VALUES ('1197b27e-3956-43dd-a75c-bfc6808dc49d', 0, 'Vital Sign', 'VITAL_SIGN', 'Vital signs.'); INSERT INTO fhir_code_category (id, version, name, code, description) @@ -1564,33 +1572,97 @@ VALUES ('9299b82e-b90a-4542-8b78-200cadff3d7d', 0, '5b37861d-9442-4e13-ac9f-88a8 -- Script that extracts Organisation Unit Reference from Patient INSERT INTO fhir_script (id, version, name, code, description, script_type, return_type, input_type, output_type) -VALUES ('a250e109-a135-42b2-8bdb-1c050c1d384c', 0, 'Org Unit Code from Patient Org', 'EXTRACT_FHIR_PATIENT_DHIS_ORG_UNIT_CODE', -'Extracts the organization unit code reference from the business identifier that is specified by the FHIR Organization of the FHIR Patient.', -'EVALUATE', 'ORG_UNIT_REF', 'FHIR_PATIENT', NULL); +VALUES ('a250e109-a135-42b2-8bdb-1c050c1d384c', 0, 'Org Unit Code from FHIR Resource', 'EXTRACT_FHIR_RESOURCE_DHIS_ORG_UNIT_CODE', +'Extracts the organization unit code reference from the input FHIR resource.', +'EVALUATE', 'ORG_UNIT_REF', NULL, NULL); INSERT INTO fhir_script_variable (script_id, variable) VALUES ('a250e109-a135-42b2-8bdb-1c050c1d384c', 'CONTEXT'); INSERT INTO fhir_script_variable (script_id, variable) VALUES ('a250e109-a135-42b2-8bdb-1c050c1d384c', 'INPUT'); INSERT INTO fhir_script_source (id, version, script_id, source_text, source_type) VALUES ('7b94feba-bcf6-4635-929a-01311b25d975', 0, 'a250e109-a135-42b2-8bdb-1c050c1d384c', -'context.createReference(identifierUtils.getReferenceIdentifier(input.managingOrganization, ''ORGANIZATION''), ''CODE'')', 'JAVASCRIPT'); +'var ref = null; +var organizationReference = null; +if (input.managingOrganization) +{ + organizationReference = input.managingOrganization; +} +if (((organizationReference == null) || organizationReference.isEmpty()) && args[''useLocations''] && input.location && !input.getLocation().isEmpty()) +{ + var location = referenceUtils.getResource(input.location); + if ((location != null) && location.managingOrganization && !location.getManagingOrganization.isEmpty()) + { + organizationReference = location.managingOrganization; + } +} +if (organizationReference != null) +{ + var code = identifierUtils.getReferenceIdentifier(organizationReference, ''ORGANIZATION''); + var mappedCode; + if (code != null) + { + mappedCode = codeUtils.getMappedCode(code, ''ORGANIZATION''); + if ((mappedCode == null) && args[''useIdentifierCode'']) + { + mappedCode = organizationUnitUtils.existsWithPrefix(code); + } + } + if (mappedCode == null) + { + mappedCode = args[''defaultCode'']; + } + if (mappedCode != null) + { + ref = context.createReference(mappedCode, ''CODE''); + } +} +if ((ref == null) && args[''useTei''] && (typeof trackedEntityInstance !== ''undefined'')) +{ + ref = context.createReference(trackedEntityInstance.organizationUnitId, ''ID''); +} +ref', 'JAVASCRIPT'); INSERT INTO fhir_script_source_version (script_source_id, fhir_version) VALUES ('7b94feba-bcf6-4635-929a-01311b25d975', 'DSTU3'); +INSERT INTO fhir_script_argument(id, version, script_id, name, data_type, mandatory, default_value, description) +VALUES ('c0175733-83fc-4de2-9cd0-a2ae6b92e991', 0, 'a250e109-a135-42b2-8bdb-1c050c1d384c', +'useLocations', 'BOOLEAN', TRUE, 'true', +'Specifies if alternatively the managing organization of an included location should be used when the input itself does not contain a managing organization.'); +INSERT INTO fhir_script_argument(id, version, script_id, name, data_type, mandatory, default_value, description) +VALUES ('33e66e7a-32cc-4a2e-8224-519e790c8ad2', 0, 'a250e109-a135-42b2-8bdb-1c050c1d384c', +'useIdentifierCode', 'BOOLEAN', TRUE, 'true', +'Specifies if the identifier code itself with the default code prefix for organizations should be used as fallback when no code mapping for the identifier code exists.'); +INSERT INTO fhir_script_argument(id, version, script_id, name, data_type, mandatory, default_value, description) +VALUES ('2db146ac-1895-48e0-9d24-e81c7f8a7033', 0, 'a250e109-a135-42b2-8bdb-1c050c1d384c', +'defaultCode', 'CODE', FALSE, null, +'Specifies the default DHIS2 organization unit code that should be used when no other matching DHIS2 organization unit cannot be found.'); +INSERT INTO fhir_script_argument(id, version, script_id, name, data_type, mandatory, default_value, description) +VALUES ('ef387255-d1df-48a3-955d-2300bedf1f99', 0, 'a250e109-a135-42b2-8bdb-1c050c1d384c', +'useTei', 'BOOLEAN', TRUE, 'true', +'Specifies if the organization unit of the tracked entity instance (if any) should be used as last fallback when no other organization unit can be found.'); INSERT INTO fhir_executable_script (id, version, script_id, name, code, description) -VALUES ('25a97bb4-7b39-4ed4-8677-db4bcaa28ccf', 0, 'a250e109-a135-42b2-8bdb-1c050c1d384c', 'Org Unit Code from Patient Org', 'EXTRACT_FHIR_PATIENT_DHIS_ORG_UNIT_CODE', -'Extracts the organization unit code reference from the business identifier that is specified by the FHIR Organization of the FHIR Patient.'); +VALUES ('25a97bb4-7b39-4ed4-8677-db4bcaa28ccf', 0, 'a250e109-a135-42b2-8bdb-1c050c1d384c', 'Org Unit Code from FHIR Resource', 'EXTRACT_FHIR_RESOURCE_DHIS_ORG_UNIT_CODE', +'Extracts the organization unit code reference from the input FHIR resource.'); +INSERT INTO fhir_executable_script_argument(id, executable_script_id, script_argument_id, override_value) +VALUES ('9de91cc0-979b-43b0-99d5-fc3ee76fd74d', '25a97bb4-7b39-4ed4-8677-db4bcaa28ccf', '33e66e7a-32cc-4a2e-8224-519e790c8ad2', 'true'); +INSERT INTO fhir_executable_script_argument(id, executable_script_id, script_argument_id, override_value) +VALUES ('b19e4cc2-0ded-4410-b5d8-5e971e48fd93', '25a97bb4-7b39-4ed4-8677-db4bcaa28ccf', '2db146ac-1895-48e0-9d24-e81c7f8a7033', NULL); --- Script that extracts GEO location from Patient +-- Script that extracts GEO location from an Address INSERT INTO fhir_script (id, version, name, code, description, script_type, return_type, input_type, output_type) -VALUES ('2263b296-9d96-4698-bc1d-17930005eef3', 0, 'GEO Location from Patient', 'EXTRACT_FHIR_PATIENT_GEO_LOCATION', -'Extracts the GEO location form FHIR Patient.', -'EVALUATE', 'LOCATION', 'FHIR_PATIENT', NULL); +VALUES ('2263b296-9d96-4698-bc1d-17930005eef3', 0, 'GEO Location from Patient', 'EXTRACT_ADDRESS_GEO_LOCATION', +'Extracts the GEO location form an address that is included in the input.', +'EVALUATE', 'LOCATION', NULL, NULL); INSERT INTO fhir_script_variable (script_id, variable) VALUES ('2263b296-9d96-4698-bc1d-17930005eef3', 'CONTEXT'); INSERT INTO fhir_script_variable (script_id, variable) VALUES ('2263b296-9d96-4698-bc1d-17930005eef3', 'INPUT'); INSERT INTO fhir_script_source (id, version, script_id, source_text, source_type) VALUES ('039ac2e6-50f2-4e4a-9e4a-dc0515560273', 0, '2263b296-9d96-4698-bc1d-17930005eef3', -'geoUtils.getLocation(addressUtils.getPrimaryAddress(input.address))', 'JAVASCRIPT'); +'var location = null; +if (input.address) +{ + location = geoUtils.getLocation(addressUtils.getPrimaryAddress(input.address)); +} +location', 'JAVASCRIPT'); INSERT INTO fhir_script_source_version (script_source_id, fhir_version) VALUES ('039ac2e6-50f2-4e4a-9e4a-dc0515560273', 'DSTU3'); INSERT INTO fhir_executable_script (id, version, script_id, name, code, description) -VALUES ('ef90531f-4438-48bd-83b3-6370dd65875a', 0, '2263b296-9d96-4698-bc1d-17930005eef3', 'GEO Location from Patient', 'EXTRACT_FHIR_PATIENT_GEO_LOCATION', -'Extracts the GEO location form FHIR Patient.'); +VALUES ('ef90531f-4438-48bd-83b3-6370dd65875a', 0, '2263b296-9d96-4698-bc1d-17930005eef3', 'GEO Location from Address', 'EXTRACT_ADDRESS_GEO_LOCATION', +'Extracts the GEO location form an address that is included in the input.'); -- Script that extracts Organisation Unit Reference from Tracked Entity Instance INSERT INTO fhir_script (id, version, name, code, description, script_type, return_type, input_type, output_type) @@ -1719,7 +1791,7 @@ VALUES ('a7b60436-9fa7-4fe4-8bf7-f5e22123a980', 0, '49d35701-2979-4b36-a9ba-4269 INSERT INTO fhir_resource_mapping (id,version,fhir_resource_type,tei_lookup_script_id,enrollment_org_lookup_script_id,event_org_lookup_script_id, enrollment_date_lookup_script_id,event_date_lookup_script_id,enrollment_loc_lookup_script_id,event_loc_lookup_script_id,effective_date_lookup_script_id) VALUES ('f1cbd84f-a3db-4aa7-a9f2-a5e547e60bed', 0, 'OBSERVATION', '1b6a2f75-cb4a-47b1-8e90-3dfb4db07d36', -'a52945b5-94b9-48d4-9c49-f67b43d9dfbc', 'a52945b5-94b9-48d4-9c49-f67b43d9dfbc', +'25a97bb4-7b39-4ed4-8677-db4bcaa28ccf', '25a97bb4-7b39-4ed4-8677-db4bcaa28ccf', 'a7b60436-9fa7-4fe4-8bf7-f5e22123a980', 'a7b60436-9fa7-4fe4-8bf7-f5e22123a980', 'bb070631-46b3-42ec-83b2-00ea219bcf50', 'bb070631-46b3-42ec-83b2-00ea219bcf50', 'a7b60436-9fa7-4fe4-8bf7-f5e22123a980'); @@ -1935,3 +2007,7 @@ INSERT INTO fhir_rule (id, version, name, description, enabled, evaluation_order VALUES ('5f9ebdc9-852e-4c83-87ca-795946aabc35', 0, 'FHIR Patient to Person', NULL, TRUE, 0, 'PATIENT', 'TRACKED_ENTITY', '9299b82e-b90a-4542-8b78-200cadff3d7d', '72451c8f-7492-4707-90b8-a3e0796de19e'); INSERT INTO fhir_tracked_entity_rule (id, tracked_entity_ref, org_lookup_script_id, loc_lookup_script_id, tracked_entity_identifier_ref, tracked_entity_identifier_fq) VALUES ('5f9ebdc9-852e-4c83-87ca-795946aabc35', 'NAME:Person', '25a97bb4-7b39-4ed4-8677-db4bcaa28ccf', 'ef90531f-4438-48bd-83b3-6370dd65875a', 'CODE:National identifier', FALSE); + +UPDATE fhir_system_code sc SET system_code_value = (SELECT system_uri FROM fhir_system s WHERE s.id=sc.system_id) || '|' || system_code; +ALTER TABLE fhir_system_code ALTER COLUMN system_code_value SET NOT NULL; +CREATE INDEX fhir_system_code_i3 ON fhir_system_code (system_code_value); diff --git a/fhir/src/main/resources/db/migration/sample/V1.0.0.0_0_2__Initial_Sample_Data.sql b/fhir/src/main/resources/db/migration/sample/V1.0.0.0_0_2__Initial_Sample_Data.sql index bd33f073..3b4c5c86 100644 --- a/fhir/src/main/resources/db/migration/sample/V1.0.0.0_0_2__Initial_Sample_Data.sql +++ b/fhir/src/main/resources/db/migration/sample/V1.0.0.0_0_2__Initial_Sample_Data.sql @@ -59,7 +59,7 @@ VALUES ('2dd51309-3319-40d2-9a1f-be2a102df4a7', 0, 'Sierra Leone Location', 'SYS INSERT INTO fhir_system (id, version, name, code, system_uri) VALUES ('c4e9ac6a-cc8f-4c73-aab6-0fa6775c0ca3', 0, 'Sierra Leone Organization', 'SYSTEM_SL_ORGANIZATION', 'http://example.sl/organizations'); INSERT INTO fhir_system (id, version, name, code, system_uri) -VALUES ('ff842c76-a529-4563-972d-216b887a3573', 0, 'Sierra Leone Patient', 'SYSTEM_SL_PATIENT', 'http://example.sl/national-patient-id'); +VALUES ('ff842c76-a529-4563-972d-216b887a3573', 0, 'Sierra Leone Patient', 'SYSTEM_SL_PATIENT', 'http://example.sl/patients'); -- Assignment of system authentication URIs of unique business identifiers for FHIR resources to the subscription. INSERT INTO fhir_remote_subscription_system (id, version, remote_subscription_id, fhir_resource_type, system_id)