Skip to content

Commit

Permalink
Implemented setup wizard.
Browse files Browse the repository at this point in the history
  • Loading branch information
volsch committed Nov 9, 2018
1 parent c9ce995 commit d173b59
Show file tree
Hide file tree
Showing 22 changed files with 729 additions and 236 deletions.
18 changes: 16 additions & 2 deletions app/src/main/java/org/dhis2/fhir/adapter/DemoClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,20 @@ public static void main( String[] args )
// Organization
///////////////

Organization hospitalOrg = new Organization();
hospitalOrg.setId( IdType.newRandomUuid() );
hospitalOrg.setName( "Connaught Hospital" );
hospitalOrg.addIdentifier()
.setSystem( "http://example.sl/organizations" )
.setValue( orgCode );

Organization org = new Organization();
org.setId( IdType.newRandomUuid() );
org.setName( "Connaught Hospital" );
org.setName( "Registration Unit" );
org.addIdentifier()
.setSystem( "http://example.sl/organizations" )
.setValue( orgCode );
.setValue( "XZY123456" );
org.setPartOf( new Reference( hospitalOrg.getIdElement() ) );

//////////////////////////////////
// Create Patient (new born child)
Expand Down Expand Up @@ -371,6 +379,12 @@ public static void main( String[] args )
.getRequest()
.setMethod( Bundle.HTTPVerb.PUT )
.setUrl( "Location?identifier=http://example.sl/locations|" + orgCode );
bundle.addEntry()
.setResource( hospitalOrg )
.setFullUrl( hospitalOrg.getId() )
.getRequest()
.setMethod( Bundle.HTTPVerb.PUT )
.setUrl( "Organization?identifier=http://example.sl/organizations|XZY123456" );
bundle.addEntry()
.setResource( org )
.setFullUrl( org.getId() )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
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;
Expand Down Expand Up @@ -76,7 +75,6 @@ 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 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
*/

import org.apache.commons.codec.binary.Hex;
import org.dhis2.fhir.adapter.rest.RestResponseEntityException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
Expand All @@ -47,6 +50,8 @@
@ConditionalOnProperty( "adapter-setup" )
public class SetupController
{
private final Logger logger = LoggerFactory.getLogger( getClass() );

protected static final int BEARER_TOKEN_BYTES = 40;

private final SetupService setupService;
Expand Down Expand Up @@ -82,8 +87,24 @@ public String submit( @Valid Setup setup, BindingResult bindingResult, Model mod
return "setup";
}

setupService.apply( setup );
return "result";
try
{
setupService.apply( setup );
}
catch ( SetupException | RestResponseEntityException e )
{
logger.error( "An error occurred when performing the setup.", e );
bindingResult.reject( "setup.error", new Object[]{ e.getMessage() }, "{0}" );
return "setup";
}
catch ( RuntimeException e )
{
logger.error( "An error occurred when performing the setup.", e );
bindingResult.reject( "setup.error", "An error occurred when performing setup. Please, check the log files for further information." );
return "setup";
}

return "setup-completed";
}

@Nonnull
Expand Down
118 changes: 70 additions & 48 deletions app/src/main/java/org/dhis2/fhir/adapter/setup/SetupService.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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.ExecutableScriptArg;
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;
Expand All @@ -43,6 +44,7 @@
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.ExecutableScriptArgRepository;
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;
Expand Down Expand Up @@ -71,8 +73,16 @@
@Service
public class SetupService
{
public static final String AUTHORIZATION_HEADER_NAME = "Authorization";

public static final String ORGANIZATION_CODE_CATEGORY = "ORGANIZATION_UNIT";

public static final String ORG_UNIT_CODE_EXECUTABLE_SCRIPT_CODE = "EXTRACT_FHIR_RESOURCE_DHIS_ORG_UNIT_CODE";

public static final String ORG_UNIT_CODE_FALLBACK_ARG_NAME = "useIdentifierCode";

public static final String ORG_UNIT_CODE_DEFAULT_CODE_ARG_NAME = "defaultCode";

private final CodeCategoryRepository codeCategoryRepository;

private final CodeRepository codeRepository;
Expand All @@ -81,16 +91,19 @@ public class SetupService

private final SystemCodeRepository systemCodeRepository;

private final ExecutableScriptArgRepository executableScriptArgRepository;

private final RemoteSubscriptionRepository remoteSubscriptionRepository;

public SetupService( @Nonnull CodeCategoryRepository codeCategoryRepository, @Nonnull CodeRepository codeRepository,
@Nonnull SystemRepository systemRepository, @Nonnull SystemCodeRepository systemCodeRepository,
@Nonnull RemoteSubscriptionRepository remoteSubscriptionRepository )
@Nonnull ExecutableScriptArgRepository executableScriptArgRepository, @Nonnull RemoteSubscriptionRepository remoteSubscriptionRepository )
{
this.codeCategoryRepository = codeCategoryRepository;
this.codeRepository = codeRepository;
this.systemRepository = systemRepository;
this.systemCodeRepository = systemCodeRepository;
this.executableScriptArgRepository = executableScriptArgRepository;
this.remoteSubscriptionRepository = remoteSubscriptionRepository;
}

Expand Down Expand Up @@ -122,45 +135,6 @@ public void apply( @Nonnull Setup setup )
createSystemCodes( setup.getOrganizationCodeSetup(), organizationSystem );
}

private void createSystemCodes( @Nonnull OrganizationCodeSetup setup, @Nonnull System organizationSystem )
{
final List<FhirDhisCodeMapping> codeMappings = setup.getCodeMappings();
if ( !codeMappings.isEmpty() )
{
final CodeCategory organizationCodeCategory = findCodeCategory( ORGANIZATION_CODE_CATEGORY );
final Map<String, Code> persistedCodes = codeRepository.saveAll( createCodes( codeMappings, organizationCodeCategory ).values() )
.stream().collect( Collectors.toMap( Code::getMappedCode, c -> c ) );

final List<SystemCode> 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<String, Code> createCodes( @Nonnull List<FhirDhisCodeMapping> codeMappings, @Nonnull CodeCategory organizationCodeCategory )
{
final Map<String, Code> 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();
Expand All @@ -174,15 +148,15 @@ private void createRemoteSubscription( @Nonnull RemoteSubscriptionSetup setup, @
remoteSubscription.setToleranceMillis( setup.getFhirSetup().getToleranceMillis() );

final SubscriptionAdapterEndpoint adapterEndpoint = new SubscriptionAdapterEndpoint();
adapterEndpoint.setBaseUrl( setup.getAdapterSetup().getBaseUrl() );
adapterEndpoint.setAuthorizationHeader( setup.getAdapterSetup().getAuthorizationHeaderValue() );
adapterEndpoint.setBaseUrl( StringUtils.trim( setup.getAdapterSetup().getBaseUrl() ) );
adapterEndpoint.setAuthorizationHeader( StringUtils.trim( 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() );
dhisEndpoint.setUsername( StringUtils.trim( setup.getDhisSetup().getUsername() ) );
dhisEndpoint.setPassword( StringUtils.trim( setup.getDhisSetup().getPassword() ) );
remoteSubscription.setDhisEndpoint( dhisEndpoint );

final SubscriptionFhirEndpoint fhirEndpoint = new SubscriptionFhirEndpoint();
Expand All @@ -191,28 +165,76 @@ private void createRemoteSubscription( @Nonnull RemoteSubscriptionSetup setup, @
StringUtils.isNotBlank( setup.getFhirSetup().getHeaderValue() ) )
{
fhirEndpoint.setHeaders( new TreeSet<>( Collections.singleton( new RequestHeader(
setup.getFhirSetup().getHeaderName(),
setup.getFhirSetup().getHeaderValue(), true ) ) ) );
StringUtils.trim( setup.getFhirSetup().getHeaderName() ),
StringUtils.trim( setup.getFhirSetup().getHeaderValue() ),
AUTHORIZATION_HEADER_NAME.equalsIgnoreCase( StringUtils.trim( setup.getFhirSetup().getHeaderName() ) ) ) ) ) );
}
remoteSubscription.setFhirEndpoint( fhirEndpoint );

final RemoteSubscriptionSystem subscriptionOrganizationSystem = new RemoteSubscriptionSystem();
subscriptionOrganizationSystem.setRemoteSubscription( remoteSubscription );
subscriptionOrganizationSystem.setFhirResourceType( FhirResourceType.ORGANIZATION );
subscriptionOrganizationSystem.setSystem( organizationSystem );
subscriptionOrganizationSystem.setCodePrefix( setup.getSystemUriSetup().getOrganizationCodePrefix() );
subscriptionOrganizationSystem.setCodePrefix( StringUtils.trim( 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() );
subscriptionPatientSystem.setCodePrefix( StringUtils.trim( setup.getSystemUriSetup().getPatientCodePrefix() ) );
remoteSubscription.getSystems().add( subscriptionPatientSystem );

remoteSubscriptionRepository.saveAndFlush( remoteSubscription );
}

private void createSystemCodes( @Nonnull OrganizationCodeSetup setup, @Nonnull System organizationSystem )
{
final List<FhirDhisCodeMapping> codeMappings = setup.getCodeMappings();
if ( !codeMappings.isEmpty() )
{
final CodeCategory organizationCodeCategory = findCodeCategory( ORGANIZATION_CODE_CATEGORY );
final Map<String, Code> persistedCodes = codeRepository.saveAll( createCodes( codeMappings, organizationCodeCategory ).values() )
.stream().collect( Collectors.toMap( Code::getMappedCode, c -> c ) );

final List<SystemCode> 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 );
}

ExecutableScriptArg scriptArg = executableScriptArgRepository.findByCodeAndName( ORG_UNIT_CODE_EXECUTABLE_SCRIPT_CODE, ORG_UNIT_CODE_FALLBACK_ARG_NAME )
.orElseThrow( () -> new SetupException( "Executable script with code " + ORG_UNIT_CODE_EXECUTABLE_SCRIPT_CODE + " and argument " + ORG_UNIT_CODE_FALLBACK_ARG_NAME + " exists." ) );
scriptArg.setOverrideValue( Boolean.valueOf( setup.isFallback() ).toString() );
executableScriptArgRepository.save( scriptArg );

scriptArg = executableScriptArgRepository.findByCodeAndName( ORG_UNIT_CODE_EXECUTABLE_SCRIPT_CODE, ORG_UNIT_CODE_DEFAULT_CODE_ARG_NAME )
.orElseThrow( () -> new SetupException( "Executable script with code " + ORG_UNIT_CODE_EXECUTABLE_SCRIPT_CODE + " and argument " + ORG_UNIT_CODE_DEFAULT_CODE_ARG_NAME + " exists." ) );
scriptArg.setOverrideValue( StringUtils.trimToNull( setup.getDefaultDhisCode() ) );
executableScriptArgRepository.save( scriptArg );
}

@Nonnull
private Map<String, Code> createCodes( @Nonnull List<FhirDhisCodeMapping> codeMappings, @Nonnull CodeCategory organizationCodeCategory )
{
final Map<String, Code> 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;
}

@Nonnull
protected System findOrCreateSystem( @Nonnull String systemUri, @Nonnull String name, @Nonnull String code, @Nonnull String description )
{
Expand Down
39 changes: 39 additions & 0 deletions app/src/main/resources/templates/setup-completed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!--
~ 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.
-->

<!DOCTYPE HTML>
<html>
<head>
<title>DHIS2 FHIR Adapter - Initial Setup Completed</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<h1>DHIS2 FHIR Adapter - Initial Setup Completed</h1>
<p>The initial setup has been completed successfully!</p>
</body>
</html>
Loading

0 comments on commit d173b59

Please sign in to comment.