From 27225717e73aca373178b93ae3f484bab18862a5 Mon Sep 17 00:00:00 2001 From: Volker Schmidt Date: Mon, 12 Nov 2018 14:22:33 +0100 Subject: [PATCH] Added more documentation for REST interfaces. --- README.md | 7 +- .../dhis2/fhir/adapter/WebSecurityConfig.java | 1 + .../setup/RemoteSubscriptionAdapterSetup.java | 1 + .../setup/RemoteSubscriptionFhirSetup.java | 1 + .../fhir/adapter/setup/SetupService.java | 3 +- .../fhir/adapter/setup/SystemUriSetup.java | 1 + .../jackson/PersistentBagConverter.java | 11 +- .../fhir/adapter/validator/EnumValue.java | 59 +++ .../adapter/validator/EnumValueValidator.java | 118 +++++ .../fhir/adapter/validator}/HttpUrl.java | 4 +- .../adapter/validator}/HttpUrlValidator.java | 6 +- .../dhis2/fhir/adapter/validator}/Uri.java | 2 +- .../fhir/adapter/validator}/UriValidator.java | 4 +- .../fhir/adapter/dhis/model/Reference.java | 18 + fhir/pom.xml | 5 + fhir/src/asciidoc/api-guide.adoc | 67 --- fhir/src/main/asciidoc/api-guide.adoc | 448 ++++++++++++++++++ .../fhir/metadata/model/AbstractRule.java | 39 +- .../adapter/fhir/metadata/model/Code.java | 4 +- .../fhir/metadata/model/CodeCategory.java | 2 +- .../adapter/fhir/metadata/model/CodeSet.java | 59 ++- .../fhir/metadata/model/CodeSetValue.java | 95 ++-- .../adapter/fhir/metadata/model/Constant.java | 21 +- .../fhir/metadata/model/ExecutableScript.java | 29 +- .../metadata/model/ExecutableScriptArg.java | 11 +- .../metadata/model/ExecutableScriptInfo.java | 26 +- .../metadata/model/MappedTrackerProgram.java | 2 +- .../model/MappedTrackerProgramStage.java | 2 +- .../fhir/metadata/model/ProgramStageRule.java | 2 + .../metadata/model/RemoteSubscription.java | 36 +- .../model/RemoteSubscriptionResource.java | 18 +- .../model/RemoteSubscriptionSystem.java | 17 + .../fhir/metadata/model/RequestHeader.java | 19 +- .../adapter/fhir/metadata/model/Script.java | 30 +- .../fhir/metadata/model/ScriptArg.java | 20 +- .../fhir/metadata/model/ScriptSource.java | 15 +- .../model/SubscriptionAdapterEndpoint.java | 13 + .../model/SubscriptionDhisEndpoint.java | 10 + .../model/SubscriptionFhirEndpoint.java | 23 +- .../adapter/fhir/metadata/model/System.java | 17 +- .../fhir/metadata/model/SystemCode.java | 12 + .../metadata/model/TrackedEntityRule.java | 14 +- .../repository/CodeSetRepository.java | 93 ++++ .../FhirResourceMappingRepository.java | 2 - .../ProgramStageRuleRepository.java | 50 ++ .../metadata/repository/RuleRepository.java | 2 +- .../repository/ScriptSourceRepository.java | 2 +- .../TrackedEntityRuleRepository.java | 50 ++ .../CustomExecutableScriptRepositoryImpl.java | 2 +- .../listener/CodeSetEventListener.java | 41 +- .../ExecutableScriptEventListener.java} | 73 +-- .../BeforeCreateSaveCodeSetValidator.java | 116 +++++ ...reCreateSaveExecutableScriptValidator.java | 38 +- ...veRemoteSubscriptionResourceValidator.java | 6 + ...CreateSaveTrackedEntityRuleValidator.java} | 10 +- .../fhir/script/impl/ScriptExecutorImpl.java | 4 +- ..._Initial.sql => V1.0.0.0_0_0__Initial.sql} | 5 +- ...=> V1.0.0.0_10_0__Initial_Sample_Data.sql} | 0 .../AbstractJpaRepositoryRestDocsTest.java | 12 +- ...toryTest.java => AbstractMockMvcTest.java} | 38 +- .../fhir/adapter/fhir/ConstrainedFields.java | 5 +- ...TestConfig.java => MockMvcTestConfig.java} | 47 +- ...java => MockMvcTestWebSecurityConfig.java} | 38 +- .../CodeCategoryRepositoryRestDocsTest.java | 52 +- .../CodeRepositoryRestDocsTest.java | 139 ++++++ .../CodeSetRepositoryRestDocsTest.java | 158 ++++++ .../ConstantRepositoryRestDocsTest.java | 128 +++++ ...xecutableScriptRepositoryRestDocsTest.java | 168 +++++++ ...oteSubscriptionRepositoryRestDocsTest.java | 197 ++++++++ ...riptionResourceRepositoryRestDocsTest.java | 141 ++++++ ...scriptionSystemRepositoryRestDocsTest.java | 149 ++++++ .../RuleRepositoryRestDocsTest.java | 176 +++++++ .../ScriptArgRepositoryRestDocsTest.java | 144 ++++++ .../ScriptRepositoryRestDocsTest.java | 137 ++++++ .../ScriptSourceRepositoryRestDocsTest.java | 134 ++++++ .../SystemCodeRepositoryRestDocsTest.java | 145 ++++++ .../SystemRepositoryRestDocsTest.java | 127 +++++ fhir/src/test/resources/data.sql | 155 +++++- .../fhir/metadata/repository/createCode.json | 7 + .../metadata/repository/createCodeSet.json | 15 + .../metadata/repository/createConstant.json | 8 + .../repository/createExecutableScript.json | 17 + .../repository/createRemoteSubscription.json | 24 + .../createRemoteSubscriptionResource.json | 6 + .../createRemoteSubscriptionSystem.json | 6 + .../metadata/repository/createScript.json | 10 + .../metadata/repository/createScriptArg.json | 9 + .../repository/createScriptSource.json | 6 + .../metadata/repository/createSystem.json | 8 + .../metadata/repository/createSystemCode.json | 5 + .../repository/createTrackedEntityRule.json | 21 + .../templates/response-fields.snippet | 11 + fhir/src/test/resources/test.properties | 4 +- pom.xml | 2 +- 94 files changed, 3890 insertions(+), 345 deletions(-) create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/validator/EnumValue.java create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/validator/EnumValueValidator.java rename {app/src/main/java/org/dhis2/fhir/adapter/setup => common/src/main/java/org/dhis2/fhir/adapter/validator}/HttpUrl.java (95%) rename {app/src/main/java/org/dhis2/fhir/adapter/setup => common/src/main/java/org/dhis2/fhir/adapter/validator}/HttpUrlValidator.java (96%) rename {app/src/main/java/org/dhis2/fhir/adapter/setup => common/src/main/java/org/dhis2/fhir/adapter/validator}/Uri.java (98%) rename {app/src/main/java/org/dhis2/fhir/adapter/setup => common/src/main/java/org/dhis2/fhir/adapter/validator}/UriValidator.java (97%) delete mode 100644 fhir/src/asciidoc/api-guide.adoc create mode 100644 fhir/src/main/asciidoc/api-guide.adoc create mode 100644 fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeSetRepository.java rename common/src/main/java/org/dhis2/fhir/adapter/jackson/PersistentSortedSetConverter.java => fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/CodeSetEventListener.java (62%) rename fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/{model/CodeSetValueId.java => repository/listener/ExecutableScriptEventListener.java} (51%) create mode 100644 fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveCodeSetValidator.java rename fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/{BeforeSaveTrackedEntityRuleValidator.java => BeforeCreateSaveTrackedEntityRuleValidator.java} (84%) rename fhir/src/main/resources/db/migration/production/{V1.0.0.0_0_1__Initial.sql => V1.0.0.0_0_0__Initial.sql} (99%) rename fhir/src/main/resources/db/migration/sample/{V1.0.0.0_0_2__Initial_Sample_Data.sql => V1.0.0.0_10_0__Initial_Sample_Data.sql} (100%) rename fhir/src/test/java/org/dhis2/fhir/adapter/fhir/{AbstractJpaRepositoryTest.java => AbstractMockMvcTest.java} (65%) rename fhir/src/test/java/org/dhis2/fhir/adapter/fhir/{TestConfig.java => MockMvcTestConfig.java} (60%) rename fhir/src/test/java/org/dhis2/fhir/adapter/fhir/{TestWebSecurityConfig.java => MockMvcTestWebSecurityConfig.java} (60%) create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeSetRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ConstantRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ExecutableScriptRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionResourceRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionSystemRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RuleRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptArgRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptSourceRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/SystemCodeRepositoryRestDocsTest.java create mode 100644 fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/SystemRepositoryRestDocsTest.java create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createCode.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createCodeSet.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createConstant.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createExecutableScript.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscription.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionResource.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionSystem.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScript.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptArg.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptSource.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystem.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystemCode.json create mode 100644 fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createTrackedEntityRule.json create mode 100644 fhir/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet diff --git a/README.md b/README.md index 41d6779f..029a13d0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This repository contains the source code of the DHIS2 FHIR Adapter. The current scope of the Adapter is to import data into DHIS2 Tracker by using FHIR Subscriptions. Event if the adapter may support more FHIR Resource types at the moment, the initial official support is for FHIR Patient resources that are transformed to DHIS2 Tracked Entity instances. -The import works on the basis of a domain specific business rule engine that decides about transformations of medical data to questionnaire-like structures (DHIS2 Tracker Programs and their Program Stages). +The import works on the basis of a domain specific business rule engine that decides about transformations of patient related medical data to questionnaire-like structures (DHIS2 Tracker Programs and their Program Stages). ![DHIS2 FHIR Adapter High Level Architecture](docs/images/DHIS2_FHIR_Adapter_High_Level_Architecture.png "DHIS2 FHIR Adapter High Level Architecture") @@ -117,3 +117,8 @@ For the initial setup of the Adapter a simple user interface is provided. To acc With the default configuration the initial setup user interface can be accessed in any web browser by using http://localhost:8081/setup. The web browser will ask for a username and password of a DHIS2 user that has privilege F_SYSTEM_SETTING. After successful authentication a setup form will be displayed with further instructions and examples. The initial setup can be made once only and the initial setup form will not be accessible anymore. + +### API for Administration and Mapping +The adapter provides REST interfaces for administration and mapping. The documentation is currently generated automatically when building the adapter. Unit test execution must not be skipped in this case when building the adapter. The documentation can be + found at docs/api-guide.html. If the Adapter has been started by command line without changing the port, then the guide is available at http://localhost:8081/docs/api-guide.html. + \ No newline at end of file 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 cbbc21d5..fd7e9595 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/WebSecurityConfig.java +++ b/app/src/main/java/org/dhis2/fhir/adapter/WebSecurityConfig.java @@ -81,6 +81,7 @@ protected void configure( @Nonnull HttpSecurity http ) throws Exception .antMatchers( HttpMethod.GET, "/favicon.ico" ).permitAll() .antMatchers( HttpMethod.GET, "/actuator/health" ).permitAll() .antMatchers( HttpMethod.GET, "/actuator/info" ).permitAll() + .antMatchers( HttpMethod.GET, "/docs/**" ).permitAll() .antMatchers( HttpMethod.OPTIONS, "/api/**" ).permitAll() .antMatchers( "/actuator/**" ).hasRole( AdapterAuthorities.ADMINISTRATION_AUTHORITY ) .anyRequest().authenticated() 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 index a8bc4ba1..5b849671 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionAdapterSetup.java +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionAdapterSetup.java @@ -29,6 +29,7 @@ */ import org.dhis2.fhir.adapter.fhir.metadata.model.SubscriptionAdapterEndpoint; +import org.dhis2.fhir.adapter.validator.HttpUrl; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; 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 index 1a4faa74..a10dcc6a 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionFhirSetup.java +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/RemoteSubscriptionFhirSetup.java @@ -31,6 +31,7 @@ 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 org.dhis2.fhir.adapter.validator.HttpUrl; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; 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 index 66d67024..96921779 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupService.java +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/SetupService.java @@ -62,7 +62,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TreeSet; import java.util.stream.Collectors; /** @@ -164,7 +163,7 @@ private void createRemoteSubscription( @Nonnull RemoteSubscriptionSetup setup, @ if ( StringUtils.isNotBlank( setup.getFhirSetup().getHeaderName() ) && StringUtils.isNotBlank( setup.getFhirSetup().getHeaderValue() ) ) { - fhirEndpoint.setHeaders( new TreeSet<>( Collections.singleton( new RequestHeader( + fhirEndpoint.setHeaders( new ArrayList<>( Collections.singleton( new RequestHeader( StringUtils.trim( setup.getFhirSetup().getHeaderName() ), StringUtils.trim( setup.getFhirSetup().getHeaderValue() ), AUTHORIZATION_HEADER_NAME.equalsIgnoreCase( StringUtils.trim( setup.getFhirSetup().getHeaderName() ) ) ) ) ) ); 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 index ece929d7..a5807c34 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/setup/SystemUriSetup.java +++ b/app/src/main/java/org/dhis2/fhir/adapter/setup/SystemUriSetup.java @@ -30,6 +30,7 @@ import org.dhis2.fhir.adapter.fhir.metadata.model.RemoteSubscriptionSystem; import org.dhis2.fhir.adapter.fhir.metadata.model.System; +import org.dhis2.fhir.adapter.validator.Uri; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; diff --git a/common/src/main/java/org/dhis2/fhir/adapter/jackson/PersistentBagConverter.java b/common/src/main/java/org/dhis2/fhir/adapter/jackson/PersistentBagConverter.java index 2c737098..3e49b6df 100644 --- a/common/src/main/java/org/dhis2/fhir/adapter/jackson/PersistentBagConverter.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/jackson/PersistentBagConverter.java @@ -32,20 +32,21 @@ import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.util.Converter; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; /** - * Converts lists (especially persistent bag) to a list. - * This is required when class name is serialized and used sorted set class - * cannot be instantiated otherwise. + * Converts persistent bags to a list. This is required when class name is serialized + * and used list class cannot be instantiated otherwise. * * @author volsch */ -public class PersistentBagConverter implements Converter, List> +public class PersistentBagConverter implements Converter, ArrayList> { @Override - public List convert( List value ) + @Nullable + public ArrayList convert( @Nullable List value ) { if ( value == null ) { diff --git a/common/src/main/java/org/dhis2/fhir/adapter/validator/EnumValue.java b/common/src/main/java/org/dhis2/fhir/adapter/validator/EnumValue.java new file mode 100644 index 00000000..87f0ccb2 --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/validator/EnumValue.java @@ -0,0 +1,59 @@ +package org.dhis2.fhir.adapter.validator; + +/* + * 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 value is a supported value. + * + * @author volsch + */ +@Constraint( validatedBy = EnumValueValidator.class ) +@Target( { ElementType.FIELD } ) +@Retention( RetentionPolicy.RUNTIME ) +public @interface EnumValue +{ + String message() default "Supported enum values {value}, unsupported {unsupported}"; + + Class> value(); + + String[] unsupported() default {}; + + String[] supported() default {}; + + Class[] groups() default {}; + + Class[] payload() default {}; +} \ No newline at end of file diff --git a/common/src/main/java/org/dhis2/fhir/adapter/validator/EnumValueValidator.java b/common/src/main/java/org/dhis2/fhir/adapter/validator/EnumValueValidator.java new file mode 100644 index 00000000..058bc9b5 --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/validator/EnumValueValidator.java @@ -0,0 +1,118 @@ +package org.dhis2.fhir.adapter.validator; + +/* + * 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 javax.annotation.Nonnull; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Validates that value is enumeration value. This is primarily used for documentation + * purpose (Spring REST Docs). + * + * @author volsch + */ +public class EnumValueValidator implements ConstraintValidator +{ + private Set> supported; + + @SuppressWarnings( "unchecked" ) + @Override + public void initialize( EnumValue enumValue ) + { + supported = new HashSet<>( getSupported( enumValue.value(), enumValue.unsupported(), enumValue.supported() ) ); + } + + @SuppressWarnings( "unchecked" ) + @Override + public boolean isValid( Object value, ConstraintValidatorContext context ) + { + if ( value == null ) + { + return true; + } + + final Collection> values; + if ( value instanceof Collection ) + { + values = (Collection>) value; + } + else + { + values = Collections.singleton( (Enum) value ); + } + + for ( final Enum v : values ) + { + if ( (v != null) && !supported.contains( v ) ) + { + { + context.buildConstraintViolationWithTemplate( "Supported values " + StringUtils.join( supported, ", " ) ); + return false; + } + } + } + return true; + } + + @SuppressWarnings( "unchecked" ) + @Nonnull + public static SortedSet> getSupported( @Nonnull Class> enumClass, @Nonnull String[] unsupportedValues, @Nonnull String[] supportedValues ) + { + final Set> unsupported = new HashSet<>(); + for ( final String value : unsupportedValues ) + { + unsupported.add( Enum.valueOf( (Class) enumClass, value ) ); + } + final SortedSet> supported = new TreeSet<>( Comparator.comparing( Enum::name ) ); + if ( supportedValues.length > 0 ) + { + for ( final String value : supportedValues ) + { + supported.add( Enum.valueOf( (Class) enumClass, value ) ); + } + } + else + { + supported.addAll( Arrays.asList( enumClass.getEnumConstants() ) ); + } + supported.removeAll( unsupported ); + return supported; + } +} diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrl.java b/common/src/main/java/org/dhis2/fhir/adapter/validator/HttpUrl.java similarity index 95% rename from app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrl.java rename to common/src/main/java/org/dhis2/fhir/adapter/validator/HttpUrl.java index dd62507b..d23037fe 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrl.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/validator/HttpUrl.java @@ -1,4 +1,4 @@ -package org.dhis2.fhir.adapter.setup; +package org.dhis2.fhir.adapter.validator; /* * Copyright (c) 2004-2018, University of Oslo @@ -45,7 +45,7 @@ @Retention( RetentionPolicy.RUNTIME ) public @interface HttpUrl { - String message() default "Not a valid HTTP/HTTPS URL."; + String message() default "Not a valid HTTP/HTTPS URL"; Class[] groups() default {}; diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrlValidator.java b/common/src/main/java/org/dhis2/fhir/adapter/validator/HttpUrlValidator.java similarity index 96% rename from app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrlValidator.java rename to common/src/main/java/org/dhis2/fhir/adapter/validator/HttpUrlValidator.java index 96bf4f80..e98035f0 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/setup/HttpUrlValidator.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/validator/HttpUrlValidator.java @@ -1,4 +1,4 @@ -package org.dhis2.fhir.adapter.setup; +package org.dhis2.fhir.adapter.validator; /* * Copyright (c) 2004-2018, University of Oslo @@ -55,13 +55,13 @@ public boolean isValid( String value, ConstraintValidatorContext context ) } catch ( MalformedURLException e ) { - context.buildConstraintViolationWithTemplate( "Not a valid URL." ); + context.buildConstraintViolationWithTemplate( "Not a valid URL" ); return false; } if ( !"http".equals( protocol ) && !"https".equals( protocol ) ) { - context.buildConstraintViolationWithTemplate( "URL must use protocol HTTP or HTTPS." ); + context.buildConstraintViolationWithTemplate( "URL must use protocol HTTP or HTTPS" ); return false; } diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/Uri.java b/common/src/main/java/org/dhis2/fhir/adapter/validator/Uri.java similarity index 98% rename from app/src/main/java/org/dhis2/fhir/adapter/setup/Uri.java rename to common/src/main/java/org/dhis2/fhir/adapter/validator/Uri.java index 8a344413..46c2f7a5 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/setup/Uri.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/validator/Uri.java @@ -1,4 +1,4 @@ -package org.dhis2.fhir.adapter.setup; +package org.dhis2.fhir.adapter.validator; /* * Copyright (c) 2004-2018, University of Oslo diff --git a/app/src/main/java/org/dhis2/fhir/adapter/setup/UriValidator.java b/common/src/main/java/org/dhis2/fhir/adapter/validator/UriValidator.java similarity index 97% rename from app/src/main/java/org/dhis2/fhir/adapter/setup/UriValidator.java rename to common/src/main/java/org/dhis2/fhir/adapter/validator/UriValidator.java index d9fc87a8..a7cad9db 100644 --- a/app/src/main/java/org/dhis2/fhir/adapter/setup/UriValidator.java +++ b/common/src/main/java/org/dhis2/fhir/adapter/validator/UriValidator.java @@ -1,4 +1,4 @@ -package org.dhis2.fhir.adapter.setup; +package org.dhis2.fhir.adapter.validator; /* * Copyright (c) 2004-2018, University of Oslo @@ -54,7 +54,7 @@ public boolean isValid( String value, ConstraintValidatorContext context ) } catch ( URISyntaxException e ) { - context.buildConstraintViolationWithTemplate( "Not a valid URI." ); + context.buildConstraintViolationWithTemplate( "Not a valid URI" ); return false; } diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/model/Reference.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/model/Reference.java index 009d32bf..09ebd0cb 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/model/Reference.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/model/Reference.java @@ -29,9 +29,15 @@ */ import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; +import org.dhis2.fhir.adapter.validator.EnumValue; import javax.annotation.Nonnull; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.Objects; @@ -46,8 +52,14 @@ public class Reference implements Serializable { private static final long serialVersionUID = 6049184293580457755L; + public static final int MAX_VALUE_LENGTH = 200; + + @NotBlank + @Size( max = MAX_VALUE_LENGTH ) private final String value; + @NotNull + @EnumValue( ReferenceType.class ) private final ReferenceType type; @Nonnull @@ -82,6 +94,12 @@ public ReferenceType getType() return type; } + @JsonIgnore + public boolean isValid() + { + return StringUtils.isNotBlank( getValue() ) && (StringUtils.length( getValue() ) <= MAX_VALUE_LENGTH); + } + @Override public boolean equals( Object o ) { diff --git a/fhir/pom.xml b/fhir/pom.xml index 8647bfee..2147fb38 100644 --- a/fhir/pom.xml +++ b/fhir/pom.xml @@ -124,6 +124,11 @@ html book + + + WARN + + diff --git a/fhir/src/asciidoc/api-guide.adoc b/fhir/src/asciidoc/api-guide.adoc deleted file mode 100644 index ded5a211..00000000 --- a/fhir/src/asciidoc/api-guide.adoc +++ /dev/null @@ -1,67 +0,0 @@ -= RESTful Notes API Guide -Andy Wilkinson; -:doctype: book -:icons: font -:source-highlighter: highlightjs -:toc: left -:toclevels: 4 -:sectlinks: -:operation-curl-request-title: Example request -:operation-http-response-title: Example response - -[[overview]] -= Overview - -[[overview-http-verbs]] -== HTTP verbs - -RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its -use of HTTP verbs. - -|=== -| Verb | Usage - -| `GET` -| Used to retrieve a resource - -| `POST` -| Used to create a new resource - -| `PATCH` -| Used to update an existing resource, including partial updates - -| `DELETE` -| Used to delete an existing resource -|=== - -[[overview-http-status-codes]] -== HTTP status codes - -RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its -use of HTTP status codes. - -|=== -| Status code | Usage - -| `200 OK` -| The request completed successfully - -| `201 Created` -| A new resource has been created successfully. The resource's URI is available from the response's -`Location` header - -| `204 No Content` -| An update to an existing resource has been applied successfully - -| `400 Bad Request` -| The request was malformed. The response body will include an error providing further information - -| `404 Not Found` -| The requested resource did not exist -|=== - -[[overview-headers]] -== Headers - -Every response has the following header(s): - diff --git a/fhir/src/main/asciidoc/api-guide.adoc b/fhir/src/main/asciidoc/api-guide.adoc new file mode 100644 index 00000000..1d8c50be --- /dev/null +++ b/fhir/src/main/asciidoc/api-guide.adoc @@ -0,0 +1,448 @@ += DHIS2 FHIR Adapter API Guide for Administration and Mapping +; +ifndef::snippets[] +:snippets: ../../../target/generated-snippets +endif::[] +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 4 +:sectlinks: +:operation-curl-request-title: Example request +:operation-http-response-title: Example response + +[[overview]] += Overview + +[[overview-http-verbs]] +== HTTP verbs + +|=== +| Verb | Usage + +| `GET` +| Used to retrieve a resource + +| `POST` +| Used to create a new resource + +| `PATCH` +| Used to update an existing resource, including partial updates (support for http://jsonpatch.com/ and https://tools.ietf.org/html/rfc7386). + +| `DELETE` +| Used to delete an existing resource +|=== + +[[overview-http-status-codes]] +== HTTP status codes + +|=== +| Status code | Usage + +| `200 OK` +| The request completed successfully + +| `201 Created` +| A new resource has been created successfully. The resource's URI is available from the response's +`Location` header + +| `204 No Content` +| An update to an existing resource has been applied successfully + +| `400 Bad Request` +| The request was malformed. The response body will include an error providing further information + +| `404 Not Found` +| The requested resource did not exist +|=== + +[[overview-authentication]] +== Authentication + +To use the API authentication is required. Currently only basic authentication is supported. The support will be +extended to OAuth2 in the future. The authentication is verified by DHIS 2 and authorities are granted by DHIS 2 as well +(mapped by the configuration of the adapter). + +[[api]] += API + +[[api-enumerations]] +== Enumeration Values + +The API re-uses several enumerations. These are described by this section. Enumerations that are used by one one API +resource are documented with the corresponding API resource. + +[[api-enumerations-data-type]] +=== Data Type +Data type enumerations are used for defining data the are passed to a script and data that is returned by a script. +Some of the data types can also be used for script arguments. In this case there is a string represenation for that +data type. + +|=== +| Value | Description + +| `BOOLEAN` +| A boolean value. The strings `true` and `false` can be used. + +| `INTEGER` +| An integer value without fraction digits. E.g. 10678 + +| `STRING` +| A string value. + +| `DOUBLE` +| A double precision floating point value. E.g. -18.83 + +| `DATE_TIME` +| A date and time value. E.g. 2018-11-13T20:11:11.204Z + +| `DATE_UNIT` +| A date unit. Possible values are YEARS, MONTHS, DAYS. + +| `WEIGHT_UNIT` +| A weight unit. Possible values are GRAM, KILO_GRAM, OUNCE, POUND. + +| `CONSTANT` +| The code of a constant resource entry. E.g. GENDER_FEMALE. + +| `CODE` +| The code of a code resource entry. E.g. VACCINE_01. + +| `LOCATION` +| A GEO location with longitude and latitude (in specified order). E.g. [-10.212,69.123] + +| `PATTERN` +| A regular expression pattern that may also contain groups. E.g. CODE_(.+) + +| `ORG_UNIT_REF` +| A reference to a organization unit (by unique ID, code or name). E.g. CODE:OU_471827 + +| `TRACKED_ENTITY_REF` +| A reference to a tracked entity type (by unique ID or name). E.g. NAME:Person + +| `TRACKED_ENTITY_ATTRIBUTE_REF` +| A reference to a tracked entity attribute (by unique ID, code or name). E.g. ID:1uw827761s21 + +| `DATA_ELEMENT_REF` +| A reference to a data element (by unique ID, code or name). E.g. NAME:CP Birth Weight + +| `PROGRAM_REF` +| A reference to a tracker program (by unique ID, name). E.g. NAME:Child Programme + +| `PROGRAM_STAGE_REF` +| A reference to a tracker program stage (bu unique ID, name). E.g. NAME:Birth + +| `FHIR_RESOURCE` +| A FHIR resource as defined by HAPI FHIR API. There is no string representation for this data type. + +| `EVENT_DECISION_TYPE` +| A decision that is made before processing an event (tracker program stage instance). Possible values are CONTINUE (use the existing event), NEW_EVENT (create a new event, just for repeatable program stages), BREAK (the complete rule is not applicable). +|=== + +[[api-resources]] +== Resources + +Resources can be divided into three groups: + +- Code mapping management. +- Data mapping management. +- Adapter administration. + +The response body resource examples in this document use by default content type application/hal+json. + + + +[[api-resources-code]] +=== Code Mapping Resources + +[[api-resources-code-constant]] +==== Constant +The constant resource enables mapping of a constant value to a configurable value. E.g. the constant GENDER_MALE can +be mapped to the DHIS 2 option set value Male. When another value is used by DHIS 2 (e.g. the letter M for Male), +then just the configurable value must be changed. The rules and transformations does not need to be changed. + +.Create resource curl snippet +include::{snippets}/create-constant/curl-request.adoc[] + +include::{snippets}/create-constant/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-constant/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-constant/response-body.adoc[] + +include::{snippets}/read-constant/response-fields.adoc[] + +[[api-resources-code-code-category]] +==== Code Category +The code category resource is used to group codes. E.g. a category can be used for defining vaccine codes +or organization unit codes. + +.Create resource curl snippet +include::{snippets}/create-code-category/curl-request.adoc[] + +include::{snippets}/create-code-category/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-code-category/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-code-category/response-body.adoc[] + +include::{snippets}/read-code-category/response-fields.adoc[] + +[[api-resources-code-code]] +==== Code +The code resource is used to define system independent codes. E.g. one code can be +created for a specific vaccine. This code can then be used by rules and transformations. +This code can the be mapped to system specific codes (e.g. country dependent +vaccine codes). + +.Create resource curl snippet +include::{snippets}/create-code/curl-request.adoc[] + +include::{snippets}/create-code/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-code/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-code/response-body.adoc[] + +include::{snippets}/read-code/response-fields.adoc[] + +[[api-resources-code-code-set]] +==== Code Set +The code category resource is used to group codes for a specific purpose. +E.g. a code set can be defined that contains all vaccines that result in +a measles immunization. This code set can then be used to check if a rule +is applicable to change measles immunization related information of a +patient. + +.Create resource curl snippet +include::{snippets}/create-code-set/curl-request.adoc[] + +include::{snippets}/create-code-set/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-code-set/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-code-set/response-body.adoc[] + +include::{snippets}/read-code-set/response-fields.adoc[] + +[[api-resources-code-system]] +==== System +The system resource is used to define coding system. E.g. the CVX standard for +vaccine codes is a coding system with a defined system URI. Also the identifier +values for national patient identifiers are defined within a coding system. + +.Create resource curl snippet +include::{snippets}/create-system/curl-request.adoc[] + +include::{snippets}/create-system/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-system/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-system/response-body.adoc[] + +include::{snippets}/read-system/response-fields.adoc[] + +[[api-resources-code-system-code]] +==== System Code +The system code resource is used to define system specific codes and map then +to internally used codes that are used by rules and transformations. E.g. a +system specific code may be a national vaccine code that is mapped to an +internal code. + +.Create resource curl snippet +include::{snippets}/create-system-code/curl-request.adoc[] + +include::{snippets}/create-system-code/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-system-code/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-system-code/response-body.adoc[] + +include::{snippets}/read-system-code/response-fields.adoc[] + +[[api-resources-data]] +=== Data Mapping Resources + +[[api-resources-data-script]] +==== Script +The script resource defines a single script with a specific purpose. The script +itself cannot be executed. Only executable scripts can be executed. There may be +more than one executable script for one script (using different script argument +values). The purpose may be a evaluation (e.g. calculating a specific date) or a +transformation (e.g. transformation from FHIR patient to DHIS 2 tracked entity +instance). For one script there may be several script sources (one for each +supported FHIR version). A single script source can also handle multiple FHIR +versions. One script may have several arguments that are passed as a single map +variable to the script. The script may require further variables to execute. + +.Create resource curl snippet +include::{snippets}/create-script/curl-request.adoc[] + +include::{snippets}/create-script/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-script/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-script/response-body.adoc[] + +include::{snippets}/read-script/response-fields.adoc[] + +[[api-resources-data-script-argument]] +==== Script Argument +The script argument resource defines a single script argument that belongs to a +script. Event of the script argument is marked as mandatory, providing a value is +not required. The final value must be specified by the executable script argument +in this case. The provided default argument value is only used when the executable +script does not define any override value for that argument + +.Create resource curl snippet +include::{snippets}/create-script-arg/curl-request.adoc[] + +include::{snippets}/create-script-arg/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-script-arg/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-script-arg/response-body.adoc[] + +include::{snippets}/read-script-arg/response-fields.adoc[] + +[[api-resources-data-script-source]] +==== Script Source +The source code of the script. There may be a source for each supported FHIR version +at most. + +.Create resource curl snippet +include::{snippets}/create-script-source/curl-request.adoc[] + +include::{snippets}/create-script-source/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-script-source/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-script-source/response-body.adoc[] + +include::{snippets}/read-script-source/response-fields.adoc[] + +[[api-resources-data-executable-script]] +==== Executable Script +The executable script resource provides the ability to create an executable +script for a script. There may be more than one executable script for one +script. Each executable script can override the script argument values of the +original scripts. E.g. a script may define an argument for the data element +reference and the unit for the weight of the person. The script may not +define a default value for the data element, but a default value for the unit +(e.g. kilogram). The executable script that processes the weight of a new born +child must then define the value for the data element reference and may +override the unit (e.g. gram because weight precision for a new born child). + +.Create resource curl snippet +include::{snippets}/create-executable-script/curl-request.adoc[] + +include::{snippets}/create-executable-script/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-executable-script/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-executable-script/response-body.adoc[] + +include::{snippets}/read-executable-script/response-fields.adoc[] + +[[api-resources-data-tracked-entity-rule]] +==== Tracked Entity Rule +The tracked entity rule resource defines a rule that is able to transform +a FHIR resource that a DHIS 2 tracked entity instance. + +.Create resource curl snippet +include::{snippets}/create-tracked-entity-rule/curl-request.adoc[] + +include::{snippets}/create-tracked-entity-rule/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-tracked-entity-rule/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-tracked-entity-rule/response-body.adoc[] + +include::{snippets}/read-tracked-entity-rule/response-fields.adoc[] + +[[api-resources-administration]] +=== Adapter Administration Resources + +[[api-resources-adm-remote-subscription]] +==== Remote Subscription +The remote subscription resource defines a single remote FHIR server and its +subscriptions for FHIR resources. + +.Create resource curl snippet +include::{snippets}/create-remote-subscription/curl-request.adoc[] + +include::{snippets}/create-remote-subscription/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-remote-subscription/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-remote-subscription/response-body.adoc[] + +include::{snippets}/read-remote-subscription/response-fields.adoc[] + +[[api-resources-adm-remote-subscription-resource]] +==== Remote Subscription Resource +The resource for remote subscription resources defines a single subscription +for a single specific FHIR resource on a single FHIR server. + +.Create resource curl snippet +include::{snippets}/create-remote-subscription-resource/curl-request.adoc[] + +include::{snippets}/create-remote-subscription-resource/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-remote-subscription-resource/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-remote-subscription-resource/response-body.adoc[] + +include::{snippets}/read-remote-subscription-resource/response-fields.adoc[] + +[[api-resources-adm-remote-subscription-system]] +==== Remote Subscription System +The remote subscription system resource defines a specific coding system that is +used for a FHIR resource. E.g. a FHIR system may use system specific codes for +patients and organizations. The codes that are provided by several FHIR systems +may overlap and are only unique in combination with a FHIR system specific +system URI. For each coding system a code prefix can be defined. E.g. if there +are ten regions in a country and the ten regions use their own FHIR server than +each region may use its own patient identifiers. These patient identifiers can be +made unique by adding a specific code prefix (e.g. two letter abbreviation of the +regions). This results in a unique national identifier. + +.Create resource curl snippet +include::{snippets}/create-remote-subscription-system/curl-request.adoc[] + +include::{snippets}/create-remote-subscription-system/request-fields.adoc[] + +.Read resource curl snippet +include::{snippets}/read-remote-subscription-system/curl-request.adoc[] + +.Read resource response body +include::{snippets}/read-remote-subscription-system/response-body.adoc[] + +include::{snippets}/read-remote-subscription-system/response-fields.adoc[] 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 a45a2787..86785e09 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 @@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.dhis2.fhir.adapter.dhis.model.DhisResourceType; +import org.dhis2.fhir.adapter.validator.EnumValue; import javax.annotation.Nonnull; import javax.persistence.Basic; @@ -48,6 +49,10 @@ import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; +import javax.persistence.Transient; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; /** @@ -65,9 +70,9 @@ @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" ) + "(SELECT 1 FROM CodeSetValue csv JOIN csv.code c JOIN c.systemCodes sc ON sc.systemCodeValue IN (:systemCodeValues) " + + "JOIN sc.system s ON s.enabled=true WHERE csv.codeSet=r.applicableCodeSet AND csv.enabled=true)))" ) } ) +@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, property = "dhisResourceType", include = JsonTypeInfo.As.EXISTING_PROPERTY ) @JsonSubTypes( { @JsonSubTypes.Type( value = TrackedEntityRule.class, name = "TRACKED_ENTITY" ), @JsonSubTypes.Type( value = ProgramStageRule.class, name = "PROGRAM_STAGE_EVENT" ) @@ -82,14 +87,29 @@ public abstract class AbstractRule extends VersionedBaseMetadata implements Seri public static final int MAX_NAME_LENGTH = 230; + @NotBlank + @Size( max = MAX_NAME_LENGTH ) private String name; + private String description; - private boolean enabled; + + private boolean enabled = true; + private int evaluationOrder; + + @NotNull + @EnumValue( DhisResourceType.class ) private DhisResourceType dhisResourceType; + + @NotNull + @EnumValue( FhirResourceType.class ) private FhirResourceType fhirResourceType; + private ExecutableScript applicableInScript; + private CodeSet applicableCodeSet; + + @NotNull private ExecutableScript transformInScript; protected AbstractRule() @@ -103,7 +123,7 @@ protected AbstractRule( @Nonnull DhisResourceType dhisResourceType ) } @Basic - @Column( name = "name", nullable = false, length = 230 ) + @Column( name = "name", nullable = false, length = 230, unique = true ) public String getName() { return name; @@ -163,20 +183,13 @@ public void setFhirResourceType( FhirResourceType fhirResourceType ) this.fhirResourceType = fhirResourceType; } - @Basic - @Column( name = "dhis_resource_type", nullable = false, updatable = false, length = 30 ) - @Enumerated( EnumType.STRING ) + @Transient @JsonProperty( access = JsonProperty.Access.READ_ONLY ) public DhisResourceType getDhisResourceType() { return dhisResourceType; } - public void setDhisResourceType( DhisResourceType dhisResourceType ) - { - this.dhisResourceType = dhisResourceType; - } - @ManyToOne @JoinColumn( name = "applicable_in_script_id", referencedColumnName = "id" ) public ExecutableScript getApplicableInScript() 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 9740ece5..2144b79f 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 @@ -29,6 +29,7 @@ */ import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.data.rest.core.annotation.RestResource; import javax.persistence.Basic; import javax.persistence.Column; @@ -98,7 +99,7 @@ public void setName( String name ) } @Basic - @Column( name = "code", nullable = false, length = 50 ) + @Column( name = "code", nullable = false, length = 50, unique = true ) public String getCode() { return code; @@ -148,6 +149,7 @@ public void setCodeCategory( CodeCategory codeCategory ) @OneToMany( mappedBy = "code" ) @OrderBy( "id" ) @JsonIgnore + @RestResource( exported = false ) public List getSystemCodes() { return systemCodes; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeCategory.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeCategory.java index 33e11a63..e0bd3bd1 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeCategory.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeCategory.java @@ -74,7 +74,7 @@ public void setName( String name ) } @Basic - @Column( name = "code", nullable = false, length = 50 ) + @Column( name = "code", nullable = false, length = 50, unique = true ) public String getCode() { return code; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSet.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSet.java index 55101eb8..8ec8fc4c 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSet.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSet.java @@ -28,31 +28,59 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.dhis2.fhir.adapter.jackson.PersistentSortedSetConverter; +import com.fasterxml.jackson.annotation.JsonFilter; +import org.dhis2.fhir.adapter.jackson.ToManyPropertyFilter; import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; +import java.util.List; import java.util.Objects; -import java.util.SortedSet; +/** + * Contains a collection of {@linkplain Code code}. E.g. there may be multiple codes + * for vaccines that cause an immunization for measles. + * + * @author volsch + */ @Entity @Table( name = "fhir_code_set" ) +@JsonFilter( ToManyPropertyFilter.FILTER_NAME ) public class CodeSet extends VersionedBaseMetadata implements Serializable { private static final long serialVersionUID = 1177970691904984600L; + public static final int MAX_NAME_LENGTH = 230; + + public static final int MAX_CODE_LENGTH = 50; + + @NotNull private CodeCategory codeCategory; + + @NotBlank + @Size( max = MAX_NAME_LENGTH ) private String name; + + @NotBlank + @Size( max = MAX_CODE_LENGTH ) private String code; + private String description; - private SortedSet codeSetValues; + + @NotNull + @Valid + private List codeSetValues; @Basic @Column( name = "name", nullable = false, length = 230 ) @@ -67,7 +95,7 @@ public void setName( String name ) } @Basic - @Column( name = "code", nullable = false, length = 50 ) + @Column( name = "code", nullable = false, length = 50, unique = true ) public String getCode() { return code; @@ -90,16 +118,27 @@ public void setDescription( String description ) this.description = description; } + @ManyToOne( optional = false ) + @JoinColumn( name = "code_category_id", nullable = false ) + public CodeCategory getCodeCategory() + { + return codeCategory; + } + + public void setCodeCategory( CodeCategory codeCategory ) + { + this.codeCategory = codeCategory; + } + @SuppressWarnings( "JpaAttributeTypeInspection" ) - @OneToMany( orphanRemoval = true, mappedBy = "id.codeSet", cascade = CascadeType.ALL ) - @OrderBy( "id.codeSet.id,id.code.id" ) - @JsonSerialize( converter = PersistentSortedSetConverter.class ) - public SortedSet getCodeSetValues() + @OneToMany( orphanRemoval = true, mappedBy = "codeSet", cascade = CascadeType.ALL ) + @OrderBy( "id" ) + public List getCodeSetValues() { return codeSetValues; } - public void setCodeSetValues( SortedSet codeSetValues ) + public void setCodeSetValues( List codeSetValues ) { if ( codeSetValues != null ) { diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSetValue.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSetValue.java index 0cdf0213..a2cb03e7 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSetValue.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSetValue.java @@ -30,78 +30,90 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.dhis2.fhir.adapter.fhir.metadata.repository.FhirAdapterMetadata; +import org.hibernate.annotations.GenericGenerator; -import javax.annotation.Nonnull; import javax.persistence.Column; -import javax.persistence.EmbeddedId; import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.Table; -import javax.persistence.Transient; +import javax.validation.constraints.NotNull; import java.io.Serializable; import java.util.Objects; +import java.util.UUID; +/** + * Contains the value of the {@linkplain CodeSet code set} which can additionally be + * enabled and disabled. + * + * @author volsch + */ @Entity @Table( name = "fhir_code_set_value" ) -public class CodeSetValue implements Serializable, Comparable, FhirAdapterMetadata +public class CodeSetValue implements Serializable, FhirAdapterMetadata { private static final long serialVersionUID = 8365594386802303061L; - private CodeSetValueId id; + private UUID id; - private boolean enabled; + private CodeSet codeSet; - @EmbeddedId - public CodeSetValueId getId() - { - return id; - } + @NotNull + private Code code; - @Column( name = "enabled", nullable = false ) - public boolean isEnabled() - { - return enabled; - } + private boolean enabled = true; - public void setEnabled( boolean enabled ) + @Override + @GeneratedValue( generator = "uuid2" ) + @GenericGenerator( name = "uuid2", strategy = "uuid2" ) + @Id + @Column( name = "id", nullable = false ) + public UUID getId() { - this.enabled = enabled; + return id; } - public void setId( CodeSetValueId id ) + public void setId( UUID id ) { this.id = id; } + @ManyToOne( optional = false ) + @JoinColumn( name = "code_set_id", nullable = false ) @JsonIgnore - @Transient public CodeSet getCodeSet() { - return (id == null) ? null : id.getCodeSet(); + return codeSet; } public void setCodeSet( CodeSet codeSet ) { - if ( id == null ) - { - id = new CodeSetValueId(); - } - id.setCodeSet( codeSet ); + this.codeSet = codeSet; } - @JsonIgnore - @Transient + @ManyToOne( optional = false ) + @JoinColumn( name = "code_id", nullable = false ) public Code getCode() { - return (id == null) ? null : id.getCode(); + return code; } public void setCode( Code code ) { - if ( id == null ) - { - id = new CodeSetValueId(); - } - id.setCode( code ); + this.code = code; + } + + @Column( name = "enabled", nullable = false ) + public boolean isEnabled() + { + return enabled; + } + + public void setEnabled( boolean enabled ) + { + this.enabled = enabled; } @Override @@ -110,24 +122,19 @@ public boolean equals( Object o ) if ( this == o ) return true; if ( o == null || getClass() != o.getClass() ) return false; CodeSetValue that = (CodeSetValue) o; - return Objects.equals( id, that.id ); + return Objects.equals( (codeSet == null) ? null : codeSet.getId(), (that.codeSet == null) ? null : that.codeSet.getId() ) && + Objects.equals( (code == null) ? null : code.getId(), (that.code == null) ? null : that.code.getId() ); } @Override public int hashCode() { - return Objects.hash( id ); + return Objects.hash( (codeSet == null) ? null : codeSet.getId(), (code == null) ? null : code.getId() ); } @Override - public int compareTo( @Nonnull CodeSetValue o ) + public String toString() { - int value = ((getCodeSet() == null) && (o.getCodeSet() == null)) ? 0 : - getCodeSet().getId().compareTo( o.getCodeSet().getId() ); - if ( value != 0 ) - { - return value; - } - return getCode().getId().compareTo( o.getCode().getId() ); + return "[" + "codeSetId=" + ((codeSet == null) ? "" : codeSet.getId()) + ", codeId=" + ((code == null) ? "" : code.getId()) + ']'; } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Constant.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Constant.java index f5e95c15..71e077af 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Constant.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Constant.java @@ -28,12 +28,17 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.dhis2.fhir.adapter.validator.EnumValue; + import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; /** @@ -54,11 +59,25 @@ public class Constant extends VersionedBaseMetadata implements Serializable public static final int MAX_VALUE_LENGTH = 250; + @NotBlank + @Size( max = MAX_NAME_LENGTH ) private String name; + private String description; + + @NotNull + @EnumValue( ConstantCategory.class ) private ConstantCategory category; + + @NotBlank + @Size( max = MAX_CODE_LENGTH ) private String code; + + @NotNull + @EnumValue( DataType.class ) private DataType dataType; + + @Size( max = MAX_VALUE_LENGTH ) private String value; @Basic @@ -99,7 +118,7 @@ public void setCategory( ConstantCategory category ) } @Basic - @Column( name = "code", nullable = false, length = 50 ) + @Column( name = "code", nullable = false, length = 50, unique = true ) public String getCode() { return code; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ExecutableScript.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ExecutableScript.java index 665a391e..13ab684d 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ExecutableScript.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ExecutableScript.java @@ -28,24 +28,23 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.dhis2.fhir.adapter.jackson.PersistentBagConverter; -import org.springframework.data.rest.core.annotation.RestResource; +import com.fasterxml.jackson.annotation.JsonFilter; +import org.dhis2.fhir.adapter.jackson.ToManyPropertyFilter; import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.List; -import java.util.UUID; /** * Contains the definition of an executable {@linkplain Script script} where values of all mandatory arguments must @@ -55,6 +54,7 @@ */ @Entity @Table( name = "fhir_executable_script" ) +@JsonFilter( ToManyPropertyFilter.FILTER_NAME ) public class ExecutableScript extends VersionedBaseMetadata implements Serializable { private static final long serialVersionUID = -2006842064596779970L; @@ -63,16 +63,23 @@ public class ExecutableScript extends VersionedBaseMetadata implements Serializa public static final int MAX_CODE_LENGTH = 100; - private UUID id; + @NotNull private Script script; + + @NotBlank + @Size( max = MAX_NAME_LENGTH ) private String name; + + @NotBlank + @Size( max = MAX_CODE_LENGTH ) private String code; + private String description; + private List overrideArguments; @ManyToOne @JoinColumn( name = "script_id", referencedColumnName = "id", nullable = false ) - @JsonIgnore public Script getScript() { return script; @@ -96,7 +103,7 @@ public void setName( String name ) } @Basic - @Column( name = "code", nullable = false ) + @Column( name = "code", nullable = false, unique = true ) public String getCode() { return code; @@ -119,10 +126,8 @@ public void setDescription( String description ) this.description = description; } - @OneToMany( mappedBy = "script", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER ) + @OneToMany( mappedBy = "script", cascade = CascadeType.ALL, orphanRemoval = true ) @OrderBy( "id" ) - @RestResource - @JsonSerialize( converter = PersistentBagConverter.class ) public List getOverrideArguments() { return overrideArguments; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ExecutableScriptArg.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ExecutableScriptArg.java index bcce9142..f7e2aa46 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ExecutableScriptArg.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ExecutableScriptArg.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.dhis2.fhir.adapter.fhir.metadata.repository.FhirAdapterMetadata; import org.hibernate.annotations.GenericGenerator; +import org.springframework.data.rest.core.annotation.RestResource; import javax.persistence.Basic; import javax.persistence.Column; @@ -40,6 +41,7 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.validation.constraints.NotNull; import java.io.Serializable; import java.util.UUID; @@ -56,10 +58,16 @@ public class ExecutableScriptArg implements Serializable, FhirAdapterMetadata executableScriptArgs; + private final Script script; + private final List scriptArgs; + private final ScriptSource scriptSource; - public ExecutableScriptInfo( @JsonProperty( "executableScript" ) @NonNull ExecutableScript executableScript, @JsonProperty( "script" ) @Nonnull Script script, @JsonProperty( "scriptSource" ) @Nonnull ScriptSource scriptSource ) + public ExecutableScriptInfo( @JsonProperty( "executableScript" ) @NonNull ExecutableScript executableScript, @JsonProperty( "executableScriptArgs" ) @Nonnull List executableScriptArgs, + @JsonProperty( "script" ) @Nonnull Script script, @JsonProperty( "scriptArgs" ) @Nonnull List scriptArgs, + @JsonProperty( "scriptSource" ) @Nonnull ScriptSource scriptSource ) { this.executableScript = executableScript; + // do not use persistence specific list implementations when serializing to JSON + this.executableScriptArgs = new ArrayList<>( executableScriptArgs ); this.script = script; + // do not use persistence specific list implementations when serializing to JSON + this.scriptArgs = new ArrayList<>( scriptArgs ); this.scriptSource = scriptSource; } @@ -68,6 +80,18 @@ public Script getScript() return script; } + @Nonnull + public List getExecutableScriptArgs() + { + return executableScriptArgs; + } + + @Nonnull + public List getScriptArgs() + { + return scriptArgs; + } + @Nonnull public ScriptSource getScriptSource() { diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/MappedTrackerProgram.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/MappedTrackerProgram.java index a6712f36..92b6aabc 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/MappedTrackerProgram.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/MappedTrackerProgram.java @@ -54,7 +54,7 @@ public class MappedTrackerProgram extends VersionedBaseMetadata implements Seria private String name; private String description; private Reference programReference; - private boolean enabled; + private boolean enabled = true; private TrackedEntityRule trackedEntityRule; private boolean creationEnabled; private ExecutableScript creationApplicableScript; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/MappedTrackerProgramStage.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/MappedTrackerProgramStage.java index b8c9b081..a5714a6f 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/MappedTrackerProgramStage.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/MappedTrackerProgramStage.java @@ -52,7 +52,7 @@ public class MappedTrackerProgramStage extends VersionedBaseMetadata implements private String name; private String description; private Reference programStageReference; - private boolean enabled; + private boolean enabled = true; private MappedTrackerProgram program; private boolean creationEnabled; private ExecutableScript creationApplicableScript; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ProgramStageRule.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ProgramStageRule.java index a3669a72..d7a6490b 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ProgramStageRule.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ProgramStageRule.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.dhis2.fhir.adapter.dhis.model.DhisResourceType; +import org.springframework.hateoas.core.Relation; import javax.persistence.Basic; import javax.persistence.Column; @@ -51,6 +52,7 @@ @Entity @Table( name = "fhir_program_stage_rule" ) @DiscriminatorValue( "PROGRAM_STAGE_EVENT" ) +@Relation( value = "rule", collectionRelation = "rules" ) public class ProgramStageRule extends AbstractRule { private static final long serialVersionUID = 3376410603952222321L; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscription.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscription.java index f3cb755d..3c91d11a 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscription.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscription.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.dhis2.fhir.adapter.fhir.model.FhirVersion; import org.dhis2.fhir.adapter.jackson.ToManyPropertyFilter; +import org.dhis2.fhir.adapter.validator.EnumValue; import javax.persistence.Basic; import javax.persistence.CascadeType; @@ -44,6 +45,11 @@ import javax.persistence.OrderBy; import javax.persistence.Table; import javax.persistence.Transient; +import javax.validation.Valid; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.List; import java.util.Set; @@ -64,18 +70,44 @@ public class RemoteSubscription extends VersionedBaseMetadata implements Seriali public static final int MAX_CODE_LENGTH = 20; + @NotBlank + @Size( max = MAX_NAME_LENGTH ) private String name; + + @NotBlank + @Size( max = MAX_CODE_LENGTH ) private String code; - private boolean enabled; + + private boolean enabled = true; + private boolean locked; + private String description; + + @NotNull + @EnumValue( FhirVersion.class ) private FhirVersion fhirVersion; + + @Min( 0 ) private int toleranceMillis; + + @NotNull + @Valid private SubscriptionDhisEndpoint dhisEndpoint; + + @NotNull + @Valid private SubscriptionFhirEndpoint fhirEndpoint; + + @NotNull + @Valid private SubscriptionAdapterEndpoint adapterEndpoint; + private List resources; + private List systems; + + @EnumValue( value = FhirResourceType.class, supported = { "PATIENT" } ) private Set autoCreatedSubscriptionResources; @Basic @@ -91,7 +123,7 @@ public void setName( String name ) } @Basic - @Column( name = "code", nullable = false, length = 20 ) + @Column( name = "code", nullable = false, length = 20, unique = true ) public String getCode() { return code; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscriptionResource.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscriptionResource.java index c0dc6dfd..22b6696c 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscriptionResource.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscriptionResource.java @@ -29,6 +29,8 @@ */ import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.dhis2.fhir.adapter.validator.EnumValue; import org.springframework.data.rest.core.annotation.RestResource; import javax.persistence.Basic; @@ -41,6 +43,8 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToOne; import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; /** @@ -54,11 +58,22 @@ public class RemoteSubscriptionResource extends VersionedBaseMetadata implements { private static final long serialVersionUID = -6797001318266984453L; + public static final int MAX_CRITERIA_PARAMETERS_LENGTH = 200; + + @NotNull + @EnumValue( FhirResourceType.class ) private FhirResourceType fhirResourceType; + + @Size( max = MAX_CRITERIA_PARAMETERS_LENGTH ) private String fhirCriteriaParameters; + private String description; + + @NotNull private RemoteSubscription remoteSubscription; + private RemoteSubscriptionResourceUpdate resourceUpdate; + private String fhirSubscriptionId; @Basic @@ -75,7 +90,7 @@ public void setFhirResourceType( FhirResourceType fhirResourceType ) } @Basic - @Column( name = "fhir_criteria_parameters", length = 200 ) + @Column( name = "fhir_criteria_parameters", length = MAX_CRITERIA_PARAMETERS_LENGTH ) public String getFhirCriteriaParameters() { return fhirCriteriaParameters; @@ -125,6 +140,7 @@ public void setResourceUpdate( RemoteSubscriptionResourceUpdate resourceUpdate ) @Basic @Column( name = "fhir_subscription_id", length = 100 ) + @JsonInclude( JsonInclude.Include.NON_NULL ) public String getFhirSubscriptionId() { return fhirSubscriptionId; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscriptionSystem.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscriptionSystem.java index 9832f7bc..2307db8f 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscriptionSystem.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RemoteSubscriptionSystem.java @@ -28,6 +28,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.dhis2.fhir.adapter.validator.EnumValue; + import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; @@ -36,8 +38,15 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; +/** + * Contains the subscription related system URI assignments. + * + * @author volsch + */ @Entity @Table( name = "fhir_remote_subscription_system" ) public class RemoteSubscriptionSystem extends VersionedBaseMetadata implements Serializable @@ -46,9 +55,17 @@ public class RemoteSubscriptionSystem extends VersionedBaseMetadata implements S public static final int MAX_CODE_PREFIX_LENGTH = 20; + @NotNull private RemoteSubscription remoteSubscription; + + @NotNull + @EnumValue( FhirResourceType.class ) private FhirResourceType fhirResourceType; + + @NotNull private System system; + + @Size( max = MAX_CODE_PREFIX_LENGTH ) private String codePrefix; @Basic diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RequestHeader.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RequestHeader.java index 6ff5788a..1a7246ce 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RequestHeader.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/RequestHeader.java @@ -30,19 +30,20 @@ import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonProperty; -import org.apache.commons.lang3.ObjectUtils; import org.dhis2.fhir.adapter.jackson.ConditionallySecuredPropertyContainer; import org.dhis2.fhir.adapter.jackson.SecuredProperty; import org.dhis2.fhir.adapter.jackson.SecuredPropertyFilter; import javax.annotation.Nonnull; import javax.persistence.Embeddable; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.Objects; @JsonFilter( SecuredPropertyFilter.FILTER_NAME ) @Embeddable -public class RequestHeader implements Serializable, Comparable, ConditionallySecuredPropertyContainer +public class RequestHeader implements Serializable, ConditionallySecuredPropertyContainer { private static final long serialVersionUID = 9147646500873557921L; @@ -50,10 +51,13 @@ public class RequestHeader implements Serializable, Comparable, C public static final int MAX_VALUE_LENGTH = 200; + @NotBlank + @Size( max = MAX_NAME_LENGTH ) private String name; @JsonProperty @SecuredProperty + @Size( max = MAX_VALUE_LENGTH ) private String value; private boolean secure; @@ -115,15 +119,4 @@ public int hashCode() { return Objects.hash( name, value ); } - - @Override - public int compareTo( @Nonnull RequestHeader o ) - { - int value = ObjectUtils.compare( getName(), o.getName() ); - if ( value != 0 ) - { - return value; - } - return ObjectUtils.compare( getValue(), o.getValue() ); - } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Script.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Script.java index bfa30b33..ba254945 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Script.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/Script.java @@ -28,10 +28,12 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.dhis2.fhir.adapter.fhir.metadata.model.jackson.ScriptVariablePersistentSortedSetConverter; -import org.dhis2.fhir.adapter.jackson.PersistentBagConverter; +import org.dhis2.fhir.adapter.jackson.ToManyPropertyFilter; +import org.dhis2.fhir.adapter.validator.EnumValue; import javax.persistence.Basic; import javax.persistence.CascadeType; @@ -46,6 +48,9 @@ import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.List; import java.util.SortedSet; @@ -59,6 +64,7 @@ */ @Entity @Table( name = "fhir_script" ) +@JsonFilter( ToManyPropertyFilter.FILTER_NAME ) public class Script extends VersionedBaseMetadata implements Serializable { private static final long serialVersionUID = 2166269559735726192L; @@ -67,15 +73,34 @@ public class Script extends VersionedBaseMetadata implements Serializable public static final int MAX_CODE_LENGTH = 50; + @NotBlank + @Size( max = MAX_NAME_LENGTH ) private String name; + private String description; + + @NotBlank + @Size( max = MAX_CODE_LENGTH ) private String code; + + @NotNull + @EnumValue( ScriptType.class ) private ScriptType scriptType; + + @NotNull + @EnumValue( DataType.class ) private DataType returnType; + + @EnumValue( TransformDataType.class ) private TransformDataType inputType; + + @EnumValue( TransformDataType.class ) private TransformDataType outputType; + private List arguments; + private SortedSet variables; + private List sources; @Basic @@ -103,7 +128,7 @@ public void setDescription( String description ) } @Basic - @Column( name = "code", nullable = false, length = 50 ) + @Column( name = "code", nullable = false, length = 50, unique = true ) public String getCode() { return code; @@ -168,7 +193,6 @@ public void setOutputType( TransformDataType outputType ) @OneToMany( mappedBy = "script" ) @OrderBy( "id" ) - @JsonSerialize( converter = PersistentBagConverter.class ) public List getArguments() { return arguments; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ScriptArg.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ScriptArg.java index 9bdde6f9..77593824 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ScriptArg.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ScriptArg.java @@ -28,7 +28,9 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonFilter; +import org.dhis2.fhir.adapter.jackson.ToOnePropertyFilter; +import org.dhis2.fhir.adapter.validator.EnumValue; import javax.annotation.Nullable; import javax.persistence.Basic; @@ -39,6 +41,9 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.regex.Pattern; @@ -50,6 +55,7 @@ */ @Entity @Table( name = "fhir_script_argument" ) +@JsonFilter( ToOnePropertyFilter.FILTER_NAME ) public class ScriptArg extends VersionedBaseMetadata implements Serializable { private static final long serialVersionUID = -5052962742547037363L; @@ -62,12 +68,23 @@ public class ScriptArg extends VersionedBaseMetadata implements Serializable protected static final String ARRAY_SEPARATOR_REGEXP = Pattern.quote( ARRAY_SEPARATOR ); + @NotBlank + @Size( max = MAX_NAME_LENGTH ) private String name; + + @NotNull + @EnumValue( DataType.class ) private DataType dataType; + private boolean mandatory; + private boolean array; + private String defaultValue; + private String description; + + @NotNull private Script script; @Basic @@ -145,7 +162,6 @@ public void setDescription( String description ) @ManyToOne( optional = false ) @JoinColumn( name = "script_id", referencedColumnName = "id", nullable = false ) - @JsonIgnore public Script getScript() { return script; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ScriptSource.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ScriptSource.java index 1a9efca1..daf4e88d 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ScriptSource.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/ScriptSource.java @@ -28,10 +28,10 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.dhis2.fhir.adapter.fhir.metadata.model.jackson.FhirVersionPersistentSortedSetConverter; import org.dhis2.fhir.adapter.fhir.model.FhirVersion; +import org.dhis2.fhir.adapter.validator.EnumValue; import javax.persistence.Basic; import javax.persistence.CollectionTable; @@ -44,6 +44,9 @@ import javax.persistence.ManyToOne; import javax.persistence.OrderBy; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.SortedSet; @@ -58,9 +61,18 @@ public class ScriptSource extends VersionedBaseMetadata implements Serializable { private static final long serialVersionUID = 6002604151209645784L; + @NotBlank private String sourceText; + + @NotNull + @EnumValue( ScriptSourceType.class ) private ScriptSourceType sourceType; + + @NotNull private Script script; + + @NotNull + @Size( min = 1 ) private SortedSet fhirVersions; @Basic @@ -90,7 +102,6 @@ public void setSourceType( ScriptSourceType language ) @ManyToOne @JoinColumn( name = "script_id", referencedColumnName = "id", nullable = false ) - @JsonIgnore public Script getScript() { return script; diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionAdapterEndpoint.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionAdapterEndpoint.java index 9593615e..903ee844 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionAdapterEndpoint.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionAdapterEndpoint.java @@ -28,11 +28,16 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.hibernate.validator.constraints.URL; + import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; /** @@ -49,8 +54,16 @@ public class SubscriptionAdapterEndpoint implements Serializable public static final int MAX_AUTHORIZATION_HEADER_LENGTH = 200; + @NotBlank + @URL + @Size( max = MAX_BASE_URL_LENGTH ) private String baseUrl; + + @NotBlank + @Size( max = MAX_AUTHORIZATION_HEADER_LENGTH ) private String authorizationHeader; + + @NotNull private SubscriptionType subscriptionType = SubscriptionType.REST_HOOK; @Basic diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionDhisEndpoint.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionDhisEndpoint.java index ef02f060..394dcedf 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionDhisEndpoint.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionDhisEndpoint.java @@ -38,6 +38,9 @@ import javax.persistence.Embeddable; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; /** @@ -55,8 +58,15 @@ public class SubscriptionDhisEndpoint implements Serializable public static final int MAX_PASSWORD_LENGTH = 200; + @NotNull private AuthenticationMethod authenticationMethod; + + @NotBlank + @Size( max = MAX_USERNAME_LENGTH ) private String username; + + @NotBlank + @Size( max = MAX_PASSWORD_LENGTH ) private String password; @Enumerated( EnumType.STRING ) diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionFhirEndpoint.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionFhirEndpoint.java index 95405982..1f1f76c9 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionFhirEndpoint.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/SubscriptionFhirEndpoint.java @@ -29,7 +29,8 @@ */ import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.dhis2.fhir.adapter.jackson.PersistentSortedSetConverter; +import org.dhis2.fhir.adapter.jackson.PersistentBagConverter; +import org.hibernate.validator.constraints.URL; import javax.persistence.Basic; import javax.persistence.CollectionTable; @@ -39,8 +40,11 @@ import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.OrderBy; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; import java.io.Serializable; -import java.util.SortedSet; +import java.util.List; /** * The configuration of the FHIR endpoint that is used by the subscription. @@ -54,10 +58,17 @@ public class SubscriptionFhirEndpoint implements Serializable public static final int MAX_BASE_URL_LENGTH = 200; + @NotBlank + @URL + @Size( max = MAX_BASE_URL_LENGTH ) private String baseUrl; + private boolean logging; + private boolean verboseLogging; - private SortedSet headers; + + @Valid + private List headers; @Basic @Column( name = "remote_base_url", nullable = false, length = 200 ) @@ -75,13 +86,13 @@ public void setBaseUrl( String baseUrl ) @ElementCollection( fetch = FetchType.EAGER ) @CollectionTable( name = "fhir_remote_subscription_header", joinColumns = @JoinColumn( name = "remote_subscription_id" ) ) @OrderBy( "name,value" ) - @JsonSerialize( converter = PersistentSortedSetConverter.class ) - public SortedSet getHeaders() + @JsonSerialize( converter = PersistentBagConverter.class ) + public List getHeaders() { return headers; } - public void setHeaders( SortedSet headers ) + public void setHeaders( List headers ) { this.headers = headers; } 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 5eb35152..e519c627 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 @@ -32,6 +32,8 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; import java.io.Serializable; @Entity @@ -46,11 +48,22 @@ public class System extends VersionedBaseMetadata implements Serializable public static final int MAX_SYSTEM_URI_LENGTH = 120; + @NotBlank + @Size( max = MAX_NAME_LENGTH ) private String name; + + @NotBlank + @Size( max = MAX_CODE_LENGTH ) private String code; + + @NotBlank + @Size( max = MAX_SYSTEM_URI_LENGTH ) private String systemUri; - private boolean enabled; + + private boolean enabled = true; + private String description; + private boolean descriptionProtected; @Basic @@ -66,7 +79,7 @@ public void setName( String name ) } @Basic - @Column( name = "code", nullable = false, length = 50 ) + @Column( name = "code", nullable = false, length = 50, unique = true ) public String getCode() { return code; 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 ce6c9807..513de938 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 @@ -38,8 +38,12 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; import javax.persistence.Table; import javax.persistence.Transient; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; /** @@ -60,9 +64,16 @@ public class SystemCode extends VersionedBaseMetadata implements Serializable public static final int MAX_SYSTEM_CODE_LENGTH = 120; + @NotNull private System system; + + @NotBlank + @Size( max = MAX_SYSTEM_CODE_LENGTH ) private String systemCode; + + @NotNull private Code code; + private String systemCodeValue; @Basic @@ -122,6 +133,7 @@ public SystemCodeValue getCalculatedSystemCodeValue() } @PrePersist + @PreUpdate protected void prePersist() { setSystemCodeValue( getSystem().getSystemUri() + SystemCodeValue.SEPARATOR + getSystemCode() ); diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/TrackedEntityRule.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/TrackedEntityRule.java index b2f4e6d9..2608aede 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/TrackedEntityRule.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/TrackedEntityRule.java @@ -40,8 +40,12 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; /** + * Rule for tracked entities. + * * @author volsch */ @Entity @@ -51,11 +55,19 @@ public class TrackedEntityRule extends AbstractRule { private static final long serialVersionUID = -3997570895838354307L; + @NotNull + @Valid private Reference trackedEntityReference; + + @NotNull private ExecutableScript orgUnitLookupScript; + + @NotNull private ExecutableScript locationLookupScript; + + @NotNull + @Valid private Reference trackedEntityIdentifierReference; - private boolean trackedEntityIdentifierFq; public TrackedEntityRule() { diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeSetRepository.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeSetRepository.java new file mode 100644 index 00000000..13e266f6 --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeSetRepository.java @@ -0,0 +1,93 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.CodeSet; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.security.access.prepost.PreAuthorize; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.UUID; + +/** + * Repository for {@link CodeSet} entities. + * + * @author volsch + */ +@CacheConfig( cacheManager = "metadataCacheManager", cacheNames = "codeSet" ) +@RepositoryRestResource +@PreAuthorize( "hasRole('CODE_MAPPING')" ) +public interface CodeSetRepository extends JpaRepository +{ + @Override + @Nonnull + @CacheEvict( allEntries = true ) + List saveAll( @Nonnull Iterable entities ); + + @Override + @Nonnull + @CachePut( key = "#a0.id" ) + @CacheEvict( allEntries = true ) + S saveAndFlush( @Nonnull S entity ); + + @Override + @Nonnull + @CachePut( key = "#a0.id" ) + @CacheEvict( allEntries = true ) + S save( @Nonnull S entity ); + + @Override + @CacheEvict( allEntries = true ) + void deleteInBatch( @Nonnull Iterable entities ); + + @Override + @CacheEvict( allEntries = true ) + void deleteAllInBatch(); + + @Override + @CacheEvict( key = "#a0" ) + void deleteById( @Nonnull UUID id ); + + @Override + @CacheEvict( key = "#a0.id" ) + void delete( @Nonnull CodeSet entity ); + + @Override + @CacheEvict( allEntries = true ) + void deleteAll( @Nonnull Iterable entities ); + + @Override + @CacheEvict( allEntries = true ) + void deleteAll(); +} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/FhirResourceMappingRepository.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/FhirResourceMappingRepository.java index 6e6914c3..c3eed3d2 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/FhirResourceMappingRepository.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/FhirResourceMappingRepository.java @@ -36,7 +36,6 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.data.jpa.repository.JpaRepository; 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; @@ -51,7 +50,6 @@ * @author volsch */ @CacheConfig( cacheManager = "metadataCacheManager", cacheNames = "resourceMapping" ) -@RepositoryRestResource @PreAuthorize( "hasRole('DATA_MAPPING')" ) public interface FhirResourceMappingRepository extends JpaRepository { diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ProgramStageRuleRepository.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ProgramStageRuleRepository.java index 55d7f10b..ec65dc61 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ProgramStageRuleRepository.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ProgramStageRuleRepository.java @@ -29,8 +29,15 @@ */ import org.dhis2.fhir.adapter.fhir.metadata.model.ProgramStageRule; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.security.access.prepost.PreAuthorize; +import javax.annotation.Nonnull; +import java.util.List; import java.util.UUID; /** @@ -38,6 +45,49 @@ * * @author volsch */ +@CacheConfig( cacheManager = "metadataCacheManager", cacheNames = "rule" ) +@RepositoryRestResource +@PreAuthorize( "hasRole('DATA_MAPPING')" ) public interface ProgramStageRuleRepository extends JpaRepository { + @Override + @Nonnull + @CacheEvict( allEntries = true ) + List saveAll( @Nonnull Iterable entities ); + + @Override + @Nonnull + @CachePut( key = "#a0.id" ) + @CacheEvict( allEntries = true ) + S saveAndFlush( @Nonnull S entity ); + + @Override + @Nonnull + @CachePut( key = "#a0.id" ) + @CacheEvict( allEntries = true ) + S save( @Nonnull S entity ); + + @Override + @CacheEvict( allEntries = true ) + void deleteInBatch( @Nonnull Iterable entities ); + + @Override + @CacheEvict( allEntries = true ) + void deleteAllInBatch(); + + @Override + @CacheEvict( key = "#a0" ) + void deleteById( @Nonnull UUID id ); + + @Override + @CacheEvict( key = "#a0.id" ) + void delete( @Nonnull ProgramStageRule entity ); + + @Override + @CacheEvict( allEntries = true ) + void deleteAll( @Nonnull Iterable entities ); + + @Override + @CacheEvict( allEntries = true ) + void deleteAll(); } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RuleRepository.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RuleRepository.java index cb68a577..8111ffd4 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RuleRepository.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RuleRepository.java @@ -46,7 +46,7 @@ * @author volsch */ @CacheConfig( cacheManager = "metadataCacheManager", cacheNames = "rule" ) -@RepositoryRestResource +@RepositoryRestResource( path = "rules" ) @PreAuthorize( "hasRole('DATA_MAPPING')" ) public interface RuleRepository extends JpaRepository, CustomRuleRepository { diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptSourceRepository.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptSourceRepository.java index 3267a6ac..01c46024 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptSourceRepository.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptSourceRepository.java @@ -46,7 +46,7 @@ * @author volsch */ @CacheConfig( cacheManager = "metadataCacheManager", cacheNames = "scriptSource" ) -@RepositoryRestResource +@RepositoryRestResource( path = "scriptSources" ) @PreAuthorize( "hasRole('DATA_MAPPING')" ) public interface ScriptSourceRepository extends JpaRepository { diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/TrackedEntityRuleRepository.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/TrackedEntityRuleRepository.java index c6abfe1f..6c0142ee 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/TrackedEntityRuleRepository.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/TrackedEntityRuleRepository.java @@ -29,8 +29,15 @@ */ import org.dhis2.fhir.adapter.fhir.metadata.model.TrackedEntityRule; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.security.access.prepost.PreAuthorize; +import javax.annotation.Nonnull; +import java.util.List; import java.util.UUID; /** @@ -38,6 +45,49 @@ * * @author volsch */ +@CacheConfig( cacheManager = "metadataCacheManager", cacheNames = "rule" ) +@RepositoryRestResource +@PreAuthorize( "hasRole('DATA_MAPPING')" ) public interface TrackedEntityRuleRepository extends JpaRepository { + @Override + @Nonnull + @CacheEvict( allEntries = true ) + List saveAll( @Nonnull Iterable entities ); + + @Override + @Nonnull + @CachePut( key = "#a0.id" ) + @CacheEvict( allEntries = true ) + S saveAndFlush( @Nonnull S entity ); + + @Override + @Nonnull + @CachePut( key = "#a0.id" ) + @CacheEvict( allEntries = true ) + S save( @Nonnull S entity ); + + @Override + @CacheEvict( allEntries = true ) + void deleteInBatch( @Nonnull Iterable entities ); + + @Override + @CacheEvict( allEntries = true ) + void deleteAllInBatch(); + + @Override + @CacheEvict( key = "#a0" ) + void deleteById( @Nonnull UUID id ); + + @Override + @CacheEvict( key = "#a0.id" ) + void delete( @Nonnull TrackedEntityRule entity ); + + @Override + @CacheEvict( allEntries = true ) + void deleteAll( @Nonnull Iterable entities ); + + @Override + @CacheEvict( allEntries = true ) + void deleteAll(); } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomExecutableScriptRepositoryImpl.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomExecutableScriptRepositoryImpl.java index 84a4edb7..517eed62 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomExecutableScriptRepositoryImpl.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/impl/CustomExecutableScriptRepositoryImpl.java @@ -90,6 +90,6 @@ public Optional findInfo( @Nullable ExecutableScript execu } Hibernate.initialize( scriptSource.getFhirVersions() ); - return Optional.of( new ExecutableScriptInfo( es, es.getScript(), scriptSource ) ); + return Optional.of( new ExecutableScriptInfo( es, es.getOverrideArguments(), es.getScript(), es.getScript().getArguments(), scriptSource ) ); } } diff --git a/common/src/main/java/org/dhis2/fhir/adapter/jackson/PersistentSortedSetConverter.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/CodeSetEventListener.java similarity index 62% rename from common/src/main/java/org/dhis2/fhir/adapter/jackson/PersistentSortedSetConverter.java rename to fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/CodeSetEventListener.java index b845832c..789fe121 100644 --- a/common/src/main/java/org/dhis2/fhir/adapter/jackson/PersistentSortedSetConverter.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/CodeSetEventListener.java @@ -1,4 +1,4 @@ -package org.dhis2.fhir.adapter.jackson; +package org.dhis2.fhir.adapter.fhir.metadata.repository.listener; /* * Copyright (c) 2004-2018, University of Oslo @@ -28,41 +28,32 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.type.TypeFactory; -import com.fasterxml.jackson.databind.util.Converter; - -import java.util.SortedSet; -import java.util.TreeSet; +import org.dhis2.fhir.adapter.fhir.metadata.model.CodeSet; +import org.springframework.core.annotation.Order; +import org.springframework.data.rest.core.event.AbstractRepositoryEventListener; +import org.springframework.stereotype.Component; /** - * Converts sorted sets (especially persistent sorted set) to a tree set. - * This is required when class name is serialized and used sorted set class - * cannot be instantiated otherwise. + * Event listener that prepares {@link CodeSet} class before saving. * * @author volsch */ -public class PersistentSortedSetConverter implements Converter, TreeSet> +@Component +@Order( value = 10 ) +public class CodeSetEventListener extends AbstractRepositoryEventListener { @Override - public TreeSet convert( SortedSet value ) + protected void onBeforeCreate( CodeSet entity ) { - if ( value == null ) - { - return null; - } - return new TreeSet<>( value ); + onBeforeSave( entity ); } @Override - public JavaType getInputType( TypeFactory typeFactory ) + protected void onBeforeSave( CodeSet entity ) { - return typeFactory.constructCollectionType( SortedSet.class, Object.class ); - } - - @Override - public JavaType getOutputType( TypeFactory typeFactory ) - { - return typeFactory.constructCollectionType( TreeSet.class, Object.class ); + if ( entity.getCodeSetValues() != null ) + { + entity.getCodeSetValues().stream().filter( csv -> csv.getCodeSet() == null ).forEach( csv -> csv.setCodeSet( entity ) ); + } } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSetValueId.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/ExecutableScriptEventListener.java similarity index 51% rename from fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSetValueId.java rename to fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/ExecutableScriptEventListener.java index 560e3f7c..eb137587 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/model/CodeSetValueId.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/ExecutableScriptEventListener.java @@ -1,4 +1,4 @@ -package org.dhis2.fhir.adapter.fhir.metadata.model; +package org.dhis2.fhir.adapter.fhir.metadata.repository.listener; /* * Copyright (c) 2004-2018, University of Oslo @@ -28,65 +28,32 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dhis2.fhir.adapter.fhir.metadata.model.ExecutableScript; +import org.springframework.core.annotation.Order; +import org.springframework.data.rest.core.event.AbstractRepositoryEventListener; +import org.springframework.stereotype.Component; -import javax.persistence.Embeddable; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import java.io.Serializable; -import java.util.Objects; - -@Embeddable -public class CodeSetValueId implements Serializable +/** + * Event listener that prepares {@link ExecutableScript} class before saving. + * + * @author volsch + */ +@Component +@Order( value = 10 ) +public class ExecutableScriptEventListener extends AbstractRepositoryEventListener { - private static final long serialVersionUID = -804758880988113845L; - - private CodeSet codeSet; - private Code code; - - @ManyToOne( optional = false ) - @JoinColumn( name = "code_set_id", nullable = false ) - @JsonIgnore - public CodeSet getCodeSet() - { - return codeSet; - } - - public void setCodeSet( CodeSet codeSet ) - { - this.codeSet = codeSet; - } - - @ManyToOne( optional = false ) - @JoinColumn( name = "code_id", nullable = false ) - public Code getCode() - { - return code; - } - - public void setCode( Code code ) - { - this.code = code; - } - - @Override - public boolean equals( Object o ) - { - if ( this == o ) return true; - if ( o == null || getClass() != o.getClass() ) return false; - CodeSetValueId that = (CodeSetValueId) o; - return Objects.equals( codeSet, that.codeSet ) && Objects.equals( code, that.code ); - } - @Override - public int hashCode() + protected void onBeforeCreate( ExecutableScript entity ) { - return Objects.hash( codeSet, code ); + onBeforeSave( entity ); } @Override - public String toString() + protected void onBeforeSave( ExecutableScript entity ) { - return "[" + "codeSetId=" + ((codeSet == null) ? "" : codeSet.getId()) + ", codeId=" + ((code == null) ? "" : code.getId()) + ']'; + if ( entity.getOverrideArguments() != null ) + { + entity.getOverrideArguments().stream().filter( oa -> oa.getScript() == null ).forEach( oa -> oa.setScript( entity ) ); + } } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveCodeSetValidator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveCodeSetValidator.java new file mode 100644 index 00000000..1bd3ab6e --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveCodeSetValidator.java @@ -0,0 +1,116 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository.validator; + +/* + * 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.CodeSet; +import org.dhis2.fhir.adapter.fhir.metadata.model.CodeSetValue; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +import javax.annotation.Nonnull; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * Spring Data REST validator for {@link CodeSet}. + * + * @author volsch + */ +@Component +public class BeforeCreateSaveCodeSetValidator implements Validator +{ + @Override + public boolean supports( @Nonnull Class clazz ) + { + return CodeSet.class.isAssignableFrom( clazz ); + } + + @Override + public void validate( Object target, @Nonnull Errors errors ) + { + final CodeSet codeSet = (CodeSet) target; + + if ( StringUtils.isBlank( codeSet.getName() ) ) + { + errors.rejectValue( "name", "CodeSet.name.blank", "Name must not be blank." ); + } + if ( StringUtils.length( codeSet.getName() ) > CodeSet.MAX_NAME_LENGTH ) + { + errors.rejectValue( "name", "CodeSet.name.length", new Object[]{ CodeSet.MAX_NAME_LENGTH }, "Name must not be longer than {0} characters." ); + } + if ( StringUtils.isBlank( codeSet.getCode() ) ) + { + errors.rejectValue( "code", "CodeSet.code.blank", "Code must not be blank." ); + } + if ( StringUtils.length( codeSet.getCode() ) > CodeSet.MAX_CODE_LENGTH ) + { + errors.rejectValue( "code", "CodeSet.code.length", new Object[]{ CodeSet.MAX_CODE_LENGTH }, "Code must not be longer than {0} characters." ); + } + if ( codeSet.getCodeCategory() == null ) + { + errors.rejectValue( "codeCategory", "CodeSet.codeCategory.null", "Code category is mandatory." ); + } + if ( codeSet.getCodeSetValues() != null ) + { + final Set codeIds = new HashSet<>(); + int index = 0; + for ( final CodeSetValue codeSetValue : codeSet.getCodeSetValues() ) + { + errors.pushNestedPath( "codeSetValues[" + index + "]" ); + + if ( codeSetValue.getCodeSet() == null ) + { + codeSetValue.setCodeSet( codeSet ); + } + else if ( !Objects.equals( codeSetValue.getCodeSet().getId(), codeSet.getId() ) ) + { + errors.rejectValue( "codeSet", "CodeSetValue.codeSet.invalid", "Code set value does not belong to code set that includes the value." ); + } + if ( codeSetValue.getCode() == null ) + { + errors.rejectValue( "code", "CodeSetValue.code.null", "Code is mandatory." ); + } + else + { + if ( !codeIds.add( codeSetValue.getCode().getId() ) ) + { + errors.rejectValue( "code", "CodeSetValue.code.duplicate", "No duplicate codes must be used." ); + } + } + + errors.popNestedPath(); + index++; + } + } + } +} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveExecutableScriptValidator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveExecutableScriptValidator.java index 8bf9f9cf..2dfd43dc 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveExecutableScriptValidator.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveExecutableScriptValidator.java @@ -38,6 +38,7 @@ import javax.annotation.Nonnull; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -89,26 +90,35 @@ public void validate( Object target, @Nonnull Errors errors ) errors.rejectValue( "code", "ExecutableScript.code.length", new Object[]{ ExecutableScript.MAX_CODE_LENGTH }, "Code must not be longer than {0} characters." ); } - if ( executableScript.getOverrideArguments() != null ) + if ( executableScript.getScript() != null ) { - final Set argIds = new HashSet<>(); - int index = 0; - for ( final ExecutableScriptArg arg : executableScript.getOverrideArguments() ) + if ( executableScript.getOverrideArguments() != null ) { - errors.pushNestedPath( "overrideArguments[" + index + "]" ); - if ( !argIds.add( arg.getArgument().getId() ) ) + final Set argIds = new HashSet<>(); + int index = 0; + for ( final ExecutableScriptArg arg : executableScript.getOverrideArguments() ) { - errors.rejectValue( null, "ExecutableScript.overrideArguments.duplicate", - new Object[]{ arg.getArgument().getId() }, "Duplicate override argument for argument {0}." ); + errors.pushNestedPath( "overrideArguments[" + index + "]" ); + if ( arg.getScript() == null ) + { + arg.setScript( executableScript ); + } + if ( !Objects.equals( arg.getScript().getId(), executableScript.getId() ) ) + { + errors.rejectValue( "script", "ExecutableScript.overrideArguments.script", + "Executable script argument does not reference executable script to which it belongs to." ); + } + if ( !argIds.add( arg.getArgument().getId() ) ) + { + errors.rejectValue( null, "ExecutableScript.overrideArguments.duplicate", + new Object[]{ arg.getArgument().getId() }, "Duplicate override argument for argument {0}." ); + } + argValidator.validate( arg, errors ); + errors.popNestedPath(); + index++; } - argValidator.validate( arg, errors ); - errors.popNestedPath(); - index++; } - } - if ( executableScript.getScript() != null ) - { final Set mandatoryArgNames = executableScript.getScript().getArguments() .stream().filter( a -> a.isMandatory() && (a.getDefaultValue() == null) ) .map( ScriptArg::getName ).collect( Collectors.toSet() ); diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveRemoteSubscriptionResourceValidator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveRemoteSubscriptionResourceValidator.java index cdba9785..f39af3e3 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveRemoteSubscriptionResourceValidator.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveRemoteSubscriptionResourceValidator.java @@ -28,6 +28,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.apache.commons.lang3.StringUtils; import org.dhis2.fhir.adapter.fhir.metadata.model.RemoteSubscriptionResource; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; @@ -58,5 +59,10 @@ public void validate( Object target, @Nonnull Errors errors ) { errors.rejectValue( "fhirResourceType", "RemoteSubscriptionResource.fhirResourceType.null", "FHIR resource type is mandatory." ); } + if ( StringUtils.length( remoteSubscriptionResource.getFhirCriteriaParameters() ) > RemoteSubscriptionResource.MAX_CRITERIA_PARAMETERS_LENGTH ) + { + errors.rejectValue( "fhirCriteriaParameters", "RemoteSubscriptionResource.fhirCriteriaParameters.length", new Object[]{ RemoteSubscriptionResource.MAX_CRITERIA_PARAMETERS_LENGTH }, + "FHIR criteria parameters must not be longer than {0} characters." ); + } } } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeSaveTrackedEntityRuleValidator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveTrackedEntityRuleValidator.java similarity index 84% rename from fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeSaveTrackedEntityRuleValidator.java rename to fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveTrackedEntityRuleValidator.java index 312a9db7..525eddcd 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeSaveTrackedEntityRuleValidator.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveTrackedEntityRuleValidator.java @@ -41,7 +41,7 @@ * @author volsch */ @Component -public class BeforeSaveTrackedEntityRuleValidator extends AbstractBeforeCreateSaveRuleValidator +public class BeforeCreateSaveTrackedEntityRuleValidator extends AbstractBeforeCreateSaveRuleValidator { @Override public boolean supports( @Nonnull Class clazz ) @@ -59,10 +59,18 @@ public void validate( Object target, @Nonnull Errors errors ) { errors.rejectValue( "trackedEntityReference", "TrackedEntityRule.trackedEntityReference.null", "Tracked entity reference is mandatory." ); } + else if ( !rule.getTrackedEntityReference().isValid() ) + { + errors.rejectValue( "trackedEntityReference", "TrackedEntityRule.trackedEntityReferenceinvalid", "Tracked entity reference is not valid." ); + } if ( rule.getTrackedEntityIdentifierReference() == null ) { errors.rejectValue( "trackedEntityIdentifierReference", "TrackedEntityRule.trackedEntityIdentifierReference.null", "Tracked entity identifier reference is mandatory." ); } + else if ( !rule.getTrackedEntityIdentifierReference().isValid() ) + { + errors.rejectValue( "trackedEntityIdentifierReference", "TrackedEntityRule.trackedEntityIdentifierReference.invalid", "Tracked entity identifier reference is not valid." ); + } BeforeCreateSaveFhirResourceMappingValidator.checkValidOrgLookupScript( errors, "TrackedEntityRule.", "orgUnitLookupScript", rule.getFhirResourceType(), rule.getOrgUnitLookupScript() ); BeforeCreateSaveFhirResourceMappingValidator.checkValidLocationLookupScript( errors, "TrackedEntityRule.", "locationLookupScript", rule.getFhirResourceType(), rule.getLocationLookupScript() ); } diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/script/impl/ScriptExecutorImpl.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/script/impl/ScriptExecutorImpl.java index 2df28c1c..275288b6 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/script/impl/ScriptExecutorImpl.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/script/impl/ScriptExecutorImpl.java @@ -161,8 +161,8 @@ protected Object convertSimpleReturnValue( @Nullable Object value ) private Map createArgs( @Nonnull ExecutableScriptInfo executableScriptInfo, @Nonnull Map arguments ) { - final Collection scriptArgs = executableScriptInfo.getScript().getArguments(); - final Collection executableScriptArgs = executableScriptInfo.getExecutableScript().getOverrideArguments(); + final Collection scriptArgs = executableScriptInfo.getScriptArgs(); + final Collection executableScriptArgs = executableScriptInfo.getExecutableScriptArgs(); final Map args = new HashMap<>(); // use default values of the script first 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_0__Initial.sql similarity index 99% rename from fhir/src/main/resources/db/migration/production/V1.0.0.0_0_1__Initial.sql rename to fhir/src/main/resources/db/migration/production/V1.0.0.0_0_0__Initial.sql index 4feb512e..08386ecd 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_0__Initial.sql @@ -448,16 +448,19 @@ COMMENT ON COLUMN fhir_code_set.description IS 'The detailed description that de COMMENT ON COLUMN fhir_code_set.code_category_id IS 'References the code category to which this code set and its codes belongs to.'; CREATE TABLE fhir_code_set_value ( + id UUID NOT NULL DEFAULT UUID_GENERATE_V4(), code_set_id UUID NOT NULL, code_id UUID NOT NULL, enabled BOOLEAN NOT NULL DEFAULT TRUE, - CONSTRAINT fhir_code_set_value_pk PRIMARY KEY (code_set_id, code_id), + CONSTRAINT fhir_code_set_value_pk PRIMARY KEY (id), + CONSTRAINT fhir_code_set_value_uk_code UNIQUE (code_set_id, code_id), CONSTRAINT fhir_code_set_value_fk1 FOREIGN KEY (code_set_id) REFERENCES fhir_code_set (id) ON DELETE CASCADE, CONSTRAINT fhir_code_set_value_fk2 FOREIGN KEY (code_id) REFERENCES fhir_code (id) ON DELETE CASCADE ); CREATE INDEX fhir_code_set_value_i1 ON fhir_code_set_value (code_id); COMMENT ON TABLE fhir_code_set_value IS 'Contains mapping between the code set and its assigned codes. The mapping for individual codes may be disabled.'; +COMMENT ON COLUMN fhir_code_set_value.id IS 'Unique ID of entity.'; COMMENT ON COLUMN fhir_code_set_value.code_set_id IS 'Contains the reference to the code set that owns this mapping.'; COMMENT ON COLUMN fhir_code_set_value.code_id IS 'Contains the reference to the code that is assigned to the code set of this mapping..'; COMMENT ON COLUMN fhir_code_set_value.enabled IS 'Disabling a code of the code set avoids that the code has to be removed from the code set. The mapping can be switched off temporarily or can be kept for historically reasons.'; 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_10_0__Initial_Sample_Data.sql similarity index 100% rename from fhir/src/main/resources/db/migration/sample/V1.0.0.0_0_2__Initial_Sample_Data.sql rename to fhir/src/main/resources/db/migration/sample/V1.0.0.0_10_0__Initial_Sample_Data.sql diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractJpaRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractJpaRepositoryRestDocsTest.java index b1e960fa..2474c72b 100644 --- a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractJpaRepositoryRestDocsTest.java +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractJpaRepositoryRestDocsTest.java @@ -32,24 +32,25 @@ import org.junit.Rule; import org.springframework.restdocs.JUnitRestDocumentation; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; -import org.springframework.test.annotation.Rollback; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.transaction.annotation.Transactional; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; /** * Abstract test class that generate REST documentation for JPA repositories. * * @author volsch */ -@Transactional -@Rollback -public abstract class AbstractJpaRepositoryRestDocsTest extends AbstractJpaRepositoryTest +//@Transactional +//@Rollback +public abstract class AbstractJpaRepositoryRestDocsTest extends AbstractMockMvcTest { + public static final String API_BASE_URI = "http://localhost:8081/api"; + @Rule public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); @@ -65,6 +66,7 @@ public void beforeAbstractJpaRepositoryRestDocsTest() preprocessResponse( removeHeaders( "X-Content-Type-Options", "X-XSS-Protection", "X-Frame-Options" ), prettyPrint() ) ); docMockMvc = MockMvcBuilders.webAppContextSetup( context ) + .apply( springSecurity() ) .apply( documentationConfiguration( restDocumentation ).uris().withPort( 8081 ) ) .alwaysDo( documentationHandler ) .build(); diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractJpaRepositoryTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractMockMvcTest.java similarity index 65% rename from fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractJpaRepositoryTest.java rename to fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractMockMvcTest.java index d2094c72..907188f3 100644 --- a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractJpaRepositoryTest.java +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/AbstractMockMvcTest.java @@ -28,51 +28,49 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import org.dhis2.fhir.adapter.fhir.data.DataBasePackage; -import org.dhis2.fhir.adapter.fhir.metadata.MetadataBasePackage; import org.dhis2.fhir.adapter.jackson.JacksonConfig; import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.restdocs.constraints.ConstraintDescriptionResolver; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; + /** * Abstract base class for JPA Repository dependent tests. * * @author volsch */ @RunWith( SpringRunner.class ) -@EnableAutoConfiguration -@ContextConfiguration( classes = { JacksonAutoConfiguration.class, WebMvcAutoConfiguration.class, SpringDataWebAutoConfiguration.class, JacksonConfig.class, TestConfig.class, TestWebSecurityConfig.class, RepositoryRestMvcConfiguration.class } ) -@ComponentScan( basePackageClasses = { DataBasePackage.class, MetadataBasePackage.class } ) -@EntityScan( basePackageClasses = { DataBasePackage.class, MetadataBasePackage.class } ) -@SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOCK ) +@SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = { JacksonConfig.class, MockMvcTestConfig.class, MockMvcTestWebSecurityConfig.class } ) @TestPropertySource( "classpath:test.properties" ) -@DataJpaTest -public abstract class AbstractJpaRepositoryTest +public abstract class AbstractMockMvcTest { + public static final String AUTHORIZATION_HEADER_NAME = "Authorization"; + + public static final String CODE_MAPPING_AUTHORIZATION_HEADER_VALUE = "Basic YWRtaW46ZGlzdHJpY3Q="; + + public static final String DATA_MAPPING_AUTHORIZATION_HEADER_VALUE = "Basic YWRtaW46ZGlzdHJpY3Q="; + + public static final String ADMINISTRATION_AUTHORIZATION_HEADER_VALUE = "Basic YWRtaW46ZGlzdHJpY3Q="; + + @Autowired + protected ConstraintDescriptionResolver constraintDescriptionResolver; + @Autowired protected WebApplicationContext context; protected MockMvc mockMvc; @Before - public void beforeAbstractJpaRepositoryTest() + public void beforeAbstractTest() { - mockMvc = MockMvcBuilders.webAppContextSetup( context ).build(); + mockMvc = MockMvcBuilders.webAppContextSetup( context ).apply( springSecurity() ).build(); } } diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/ConstrainedFields.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/ConstrainedFields.java index 52ac6f1c..003d9b1a 100644 --- a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/ConstrainedFields.java +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/ConstrainedFields.java @@ -28,6 +28,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.springframework.restdocs.constraints.ConstraintDescriptionResolver; import org.springframework.restdocs.constraints.ConstraintDescriptions; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.util.StringUtils; @@ -46,9 +47,9 @@ public class ConstrainedFields { private final ConstraintDescriptions constraintDescriptions; - public ConstrainedFields( @Nonnull Class input ) + public ConstrainedFields( @Nonnull Class input, @Nonnull ConstraintDescriptionResolver descriptionResolver ) { - this.constraintDescriptions = new ConstraintDescriptions( input ); + constraintDescriptions = new ConstraintDescriptions( input, descriptionResolver ); } public FieldDescriptor withPath( @Nonnull String path ) diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/TestConfig.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/MockMvcTestConfig.java similarity index 60% rename from fhir/src/test/java/org/dhis2/fhir/adapter/fhir/TestConfig.java rename to fhir/src/test/java/org/dhis2/fhir/adapter/fhir/MockMvcTestConfig.java index 4ff2ab2a..f8efbcca 100644 --- a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/TestConfig.java +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/MockMvcTestConfig.java @@ -28,19 +28,32 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.apache.commons.lang3.StringUtils; import org.dhis2.fhir.adapter.converter.ZonedDateTimeToDateConverter; +import org.dhis2.fhir.adapter.fhir.data.DataBasePackage; +import org.dhis2.fhir.adapter.fhir.metadata.MetadataBasePackage; import org.dhis2.fhir.adapter.script.ScriptCompiler; import org.dhis2.fhir.adapter.script.impl.ScriptCompilerImpl; -import org.springframework.boot.test.autoconfigure.restdocs.RestDocsAutoConfiguration; +import org.dhis2.fhir.adapter.validator.EnumValue; +import org.dhis2.fhir.adapter.validator.EnumValueValidator; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration; +import org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy; import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter; import org.springframework.format.FormatterRegistry; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.restdocs.constraints.Constraint; +import org.springframework.restdocs.constraints.ConstraintDescriptionResolver; +import org.springframework.restdocs.constraints.ResourceBundleConstraintDescriptionResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Nonnull; @@ -53,8 +66,9 @@ * @author volsch */ @Configuration -@ContextConfiguration( classes = { RestDocsAutoConfiguration.class } ) -public class TestConfig +@EnableAutoConfiguration( exclude = { RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class, ArtemisAutoConfiguration.class, HystrixAutoConfiguration.class } ) +@ComponentScan( basePackageClasses = { DataBasePackage.class, MetadataBasePackage.class } ) +public class MockMvcTestConfig { @Nonnull @Bean @@ -98,4 +112,29 @@ public void configureRepositoryRestConfiguration( RepositoryRestConfiguration co } }; } + + @Nonnull + @Primary + @Bean + protected ConstraintDescriptionResolver constraintDescriptionResolver() + { + return new ResourceBundleConstraintDescriptionResolver() + { + @Override + public String resolveDescription( Constraint constraint ) + { + if ( EnumValue.class.getName().equals( constraint.getName() ) ) + { + @SuppressWarnings( "unchecked" ) final Class> value = (Class>) constraint.getConfiguration().get( "value" ); + final String[] unsupported = (String[]) constraint.getConfiguration().get( "unsupported" ); + final String[] supported = (String[]) constraint.getConfiguration().get( "supported" ); + if ( value != null ) + { + return "Supported values are " + StringUtils.join( EnumValueValidator.getSupported( value, unsupported, supported ), ", " ); + } + } + return super.resolveDescription( constraint ); + } + }; + } } diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/TestWebSecurityConfig.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/MockMvcTestWebSecurityConfig.java similarity index 60% rename from fhir/src/test/java/org/dhis2/fhir/adapter/fhir/TestWebSecurityConfig.java rename to fhir/src/test/java/org/dhis2/fhir/adapter/fhir/MockMvcTestWebSecurityConfig.java index 0b4b55b8..33c78e2a 100644 --- a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/TestWebSecurityConfig.java +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/MockMvcTestWebSecurityConfig.java @@ -28,14 +28,22 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.dhis2.fhir.adapter.fhir.security.AdapterAuthorities; 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; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Objects; /** * Security configuration of tests. @@ -44,7 +52,7 @@ */ @Configuration @EnableWebSecurity -public class TestWebSecurityConfig extends WebSecurityConfigurerAdapter +public class MockMvcTestWebSecurityConfig extends WebSecurityConfigurerAdapter { protected static final String DHIS_BASIC_REALM = "DHIS2"; @@ -60,4 +68,32 @@ protected void configure( @Nonnull HttpSecurity http ) throws Exception .and() .httpBasic().realmName( DHIS_BASIC_REALM ); } + + @Override + protected void configure( @Nonnull AuthenticationManagerBuilder auth ) throws Exception + { + auth.userDetailsService( username -> { + if ( !"admin" .equals( username ) ) + { + throw new UsernameNotFoundException( "Could not find user: " + username ); + } + return new User( "2h2maqu827d", "district", Arrays.asList( + new SimpleGrantedAuthority( AdapterAuthorities.CODE_MAPPING_AUTHORITY_ROLE ), + new SimpleGrantedAuthority( AdapterAuthorities.DATA_MAPPING_AUTHORITY_ROLE ), + new SimpleGrantedAuthority( AdapterAuthorities.ADMINISTRATION_AUTHORITY ) ) ); + } ).passwordEncoder( new PasswordEncoder() + { + @Override + public String encode( CharSequence rawPassword ) + { + return String.valueOf( rawPassword ); + } + + @Override + public boolean matches( CharSequence rawPassword, String encodedPassword ) + { + return Objects.equals( String.valueOf( rawPassword ), encodedPassword ); + } + } ); + } } diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeCategoryRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeCategoryRepositoryRestDocsTest.java index 5bf604fb..614c318f 100644 --- a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeCategoryRepositoryRestDocsTest.java +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeCategoryRepositoryRestDocsTest.java @@ -33,18 +33,22 @@ import org.dhis2.fhir.adapter.fhir.ConstrainedFields; import org.dhis2.fhir.adapter.fhir.metadata.model.CodeCategory; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; import org.springframework.http.MediaType; import org.springframework.restdocs.payload.JsonFieldType; -import org.springframework.security.test.context.support.WithMockUser; +import javax.annotation.Nonnull; import java.util.Objects; import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** @@ -54,13 +58,15 @@ */ public class CodeCategoryRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest { + @Autowired + private CodeCategoryRepository codeCategoryRepository; + @Test - @WithMockUser( username = "2h2maqu827d", password = "code_test", roles = "CODE_MAPPING" ) public void createCodeCategory() throws Exception { - final ConstrainedFields fields = new ConstrainedFields( CodeCategory.class ); - final String location = docMockMvc.perform( post( "/api/codeCategories" ).contentType( MediaType.APPLICATION_JSON ) - .content( IOUtils.resourceToByteArray( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createCodeCategory.json" ) ) ) + final ConstrainedFields fields = new ConstrainedFields( CodeCategory.class, constraintDescriptionResolver ); + final String location = docMockMvc.perform( post( "/api/codeCategories" ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( IOUtils.resourceToByteArray( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createCodeCategory.json" ) ) ) .andExpect( status().isCreated() ) .andExpect( header().exists( "Location" ) ) .andDo( documentationHandler.document( requestFields( @@ -71,11 +77,41 @@ public void createCodeCategory() throws Exception ) ) ).andReturn().getResponse().getHeader( "Location" ); mockMvc - .perform( get( Objects.requireNonNull( location ) ) ) + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) .andExpect( jsonPath( "name", is( "Test Code Category" ) ) ) .andExpect( jsonPath( "code", is( "TEST_CODE_CATEGORY" ) ) ) + .andExpect( jsonPath( "description", is( "This is a test code category." ) ) ) .andExpect( jsonPath( "_links.self.href", is( location ) ) ); } + + @Test + public void readCodeCategory() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( CodeCategory.class, constraintDescriptionResolver ); + final String codeCategoryId = loadCodeCategory( "ORGANIZATION_UNIT" ).getId().toString(); + docMockMvc.perform( get( "/api/codeCategories/{codeCategoryId}", codeCategoryId ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "codeCategory" ).description( "Link to this resource itself." ) ), responseFields( + attributes( key( "title" ).value( "Fields for code category reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The unique name of the code category." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the code category." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the code category is used." ).type( JsonFieldType.STRING ).optional(), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected CodeCategory loadCodeCategory( @Nonnull String code ) + { + final CodeCategory example = new CodeCategory(); + example.setCode( code ); + return codeCategoryRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Code category does not exist: " + code ) ); + } } \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeRepositoryRestDocsTest.java new file mode 100644 index 00000000..01fb4374 --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeRepositoryRestDocsTest.java @@ -0,0 +1,139 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.Code; +import org.dhis2.fhir.adapter.fhir.metadata.model.CodeCategory; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link CodeRepository}. + * + * @author volsch + */ +public class CodeRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private CodeCategoryRepository codeCategoryRepository; + + @Autowired + private CodeRepository codeRepository; + + @Test + public void createCode() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( Code.class, constraintDescriptionResolver ); + final String codeCategoryId = loadCodeCategory( "ORGANIZATION_UNIT" ).getId().toString(); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createCode.json", StandardCharsets.UTF_8 ) + .replace( "$codeCategory", API_BASE_URI + "/codeCategories/" + codeCategoryId ); + final String location = docMockMvc.perform( post( "/api/codes" ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for code creation" ) ), + fields.withPath( "name" ).description( "The unique name of the code." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the code." ).type( JsonFieldType.STRING ), + fields.withPath( "mappedCode" ).description( "The optional mapped code (e.g. organization unit code as it exists on DHIS2). If this is not specified the code itself is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the code is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "codeCategory" ).description( "The reference to the code category to which this code belongs to." ).type( JsonFieldType.STRING ) + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "name", is( "Test Code" ) ) ) + .andExpect( jsonPath( "code", is( "TEST_CODE" ) ) ) + .andExpect( jsonPath( "mappedCode", is( "MAPPED_TEST_CODE" ) ) ) + .andExpect( jsonPath( "description", is( "This is a test code." ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ) + .andExpect( jsonPath( "_links.codeCategory.href", is( location + "/codeCategory" ) ) ); + } + + @Test + public void readCode() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( Code.class, constraintDescriptionResolver ); + final String codeId = loadCode( "OU_FT_CH" ).getId().toString(); + docMockMvc.perform( get( "/api/codes/{codeId}", codeId ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "code" ).description( "Link to this resource itself." ), + linkWithRel( "codeCategory" ).description( "Link to this resource itself." ) ), responseFields( + attributes( key( "title" ).value( "Fields for code category reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The unique name of the code." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the code." ).type( JsonFieldType.STRING ), + fields.withPath( "mappedCode" ).description( "The optional mapped code (e.g. organization unit code as it exists on DHIS2). If this is not specified the code itself is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the code is used." ).type( JsonFieldType.STRING ).optional(), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected Code loadCode( @Nonnull String code ) + { + final Code example = new Code(); + example.setCode( code ); + return codeRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Code does not exist: " + code ) ); + } + + @Nonnull + protected CodeCategory loadCodeCategory( @Nonnull String code ) + { + final CodeCategory example = new CodeCategory(); + example.setCode( code ); + return codeCategoryRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Code category does not exist: " + code ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeSetRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeSetRepositoryRestDocsTest.java new file mode 100644 index 00000000..4a9587cd --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/CodeSetRepositoryRestDocsTest.java @@ -0,0 +1,158 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +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.CodeSet; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link CodeSetRepository}. + * + * @author volsch + */ +public class CodeSetRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private CodeSetRepository codeSetRepository; + + @Autowired + private CodeCategoryRepository codeCategoryRepository; + + @Autowired + private CodeRepository codeRepository; + + @Test + public void createCodeSet() throws Exception + { + final CodeCategory codeCategory = loadCodeCategory( "VACCINE" ); + final Code code1 = loadCode( "VACCINE_20" ); + final Code code2 = loadCode( "VACCINE_106" ); + + final ConstrainedFields fields = new ConstrainedFields( CodeSet.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createCodeSet.json", StandardCharsets.UTF_8 ) + .replace( "$codeCategoryId", API_BASE_URI + "/codeCategories/" + codeCategory.getId().toString() ) + .replace( "$codeId1", API_BASE_URI + "/codes/" + code1.getId().toString() ) + .replace( "$codeId2", API_BASE_URI + "/codes/" + code2.getId().toString() ); + final String location = docMockMvc.perform( post( "/api/codeSets" ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for code set creation" ) ), + fields.withPath( "name" ).description( "The unique name of the code set." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the code set." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the code set is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "codeCategory" ).description( "The code category reference to which the code set belongs to." ).type( JsonFieldType.STRING ), + fields.withPath( "codeSetValues" ).description( "The codes that belong to this code set." ).type( JsonFieldType.ARRAY ).optional(), + fields.withPath( "codeSetValues[].code" ).description( "The included code reference (must be unique in the code set)." ).type( JsonFieldType.STRING ), + fields.withPath( "codeSetValues[].enabled" ).description( "Specifies if the code is enabled in the code set." ).type( JsonFieldType.BOOLEAN ).optional() + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "name", is( "Vaccine Measles" ) ) ) + .andExpect( jsonPath( "code", is( "ALL_MEASLES" ) ) ) + .andExpect( jsonPath( "description", is( "All measles vaccine codes." ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readCodeSet() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( CodeSet.class, constraintDescriptionResolver ); + final String codeSetId = loadCodeSet( "ALL_DTP_DTAP" ).getId().toString(); + docMockMvc.perform( get( "/api/codeSets/{codeSetId}", codeSetId ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "codeSet" ).description( "Link to this resource itself." ), + linkWithRel( "codeCategory" ).description( "Link to the code category resource to which this code set belongs to." ) ), responseFields( + attributes( key( "title" ).value( "Fields for code set reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The unique name of the code set." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the code set." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the code set is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "codeSetValues" ).description( "The codes that belong to this code set." ).type( JsonFieldType.ARRAY ).optional(), + fields.withPath( "codeSetValues[].enabled" ).description( "Specifies if the code is enabled in the code set." ).type( JsonFieldType.BOOLEAN ).optional(), + subsectionWithPath( "_links" ).description( "Links to other resources" ), + subsectionWithPath( "codeSetValues[]._links" ).description( "Links to other resources (e.g. the referenced code)" ) + ) ) ); + } + + @Nonnull + protected CodeSet loadCodeSet( @Nonnull String code ) + { + final CodeSet example = new CodeSet(); + example.setCode( code ); + return codeSetRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "CodeSet does not exist: " + code ) ); + } + + @Nonnull + protected Code loadCode( @Nonnull String code ) + { + final Code example = new Code(); + example.setCode( code ); + return codeRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Code does not exist: " + code ) ); + } + + @Nonnull + protected CodeCategory loadCodeCategory( @Nonnull String code ) + { + final CodeCategory example = new CodeCategory(); + example.setCode( code ); + return codeCategoryRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Code category does not exist: " + code ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ConstantRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ConstantRepositoryRestDocsTest.java new file mode 100644 index 00000000..33b23c0d --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ConstantRepositoryRestDocsTest.java @@ -0,0 +1,128 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.Constant; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link ConstantRepository}. + * + * @author volsch + */ +public class ConstantRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private ConstantRepository constantRepository; + + @Test + public void createConstant() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( Constant.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createConstant.json", StandardCharsets.UTF_8 ); + final String location = docMockMvc.perform( post( "/api/constants" ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for constant creation" ) ), + fields.withPath( "name" ).description( "The unique name of the constant." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the constant." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the constant is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "category" ).description( "The constant category to which the constant belongs to." ).type( JsonFieldType.STRING ), + fields.withPath( "dataType" ).description( "The data type of the constant value." ).type( JsonFieldType.STRING ), + fields.withPath( "value" ).description( "The value of the constant (must have the specified data type)." ).type( JsonFieldType.STRING ).optional() + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "name", is( "Gender Female" ) ) ) + .andExpect( jsonPath( "code", is( "GENDER_FEMALE" ) ) ) + .andExpect( jsonPath( "description", is( "Constant for Gender option value as it is used by DHIS2." ) ) ) + .andExpect( jsonPath( "category", is( "GENDER" ) ) ) + .andExpect( jsonPath( "dataType", is( "STRING" ) ) ) + .andExpect( jsonPath( "value", is( "Female" ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readConstant() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( Constant.class, constraintDescriptionResolver ); + final String constantId = loadConstant( "GENDER_MALE" ).getId().toString(); + docMockMvc.perform( get( "/api/constants/{constantId}", constantId ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "constant" ).description( "Link to this resource itself." ) ), responseFields( + attributes( key( "title" ).value( "Fields for constant reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The unique name of the constant." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the constant." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the constant is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "category" ).description( "The constant category to which the constant belongs to." ).type( JsonFieldType.STRING ), + fields.withPath( "dataType" ).description( "The data type of the constant value." ).type( JsonFieldType.STRING ), + fields.withPath( "value" ).description( "The data type of the constant value (must have the specified data type)." ).type( JsonFieldType.STRING ), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected Constant loadConstant( @Nonnull String code ) + { + final Constant example = new Constant(); + example.setCode( code ); + return constantRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Constant does not exist: " + code ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ExecutableScriptRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ExecutableScriptRepositoryRestDocsTest.java new file mode 100644 index 00000000..94beb708 --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ExecutableScriptRepositoryRestDocsTest.java @@ -0,0 +1,168 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.ExecutableScript; +import org.dhis2.fhir.adapter.fhir.metadata.model.Script; +import org.dhis2.fhir.adapter.fhir.metadata.model.ScriptArg; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link ExecutableScriptRepository}. + * + * @author volsch + */ +public class ExecutableScriptRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private ScriptRepository scriptRepository; + + @Autowired + private ScriptArgRepository scriptArgRepository; + + @Autowired + private ExecutableScriptRepository executableScriptRepository; + + @Test + public void createExecutableScript() throws Exception + { + final Script script = loadScript( "TRANSFORM_FHIR_OB_BODY_WEIGHT" ); + final String scriptId = script.getId().toString(); + final String dataElementScriptArgId = loadScriptArg( script, "dataElement" ).getId().toString(); + final String roundScriptArgId = loadScriptArg( script, "round" ).getId().toString(); + + final ConstrainedFields fields = new ConstrainedFields( ExecutableScript.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createExecutableScript.json", StandardCharsets.UTF_8 ) + .replace( "$scriptId", API_BASE_URI + "/script/" + scriptId ) + .replace( "$dataElementArgId", API_BASE_URI + "/scriptArgs/" + dataElementScriptArgId ) + .replace( "$roundArgId", API_BASE_URI + "/scriptArgs/" + roundScriptArgId ); + final String location = docMockMvc.perform( post( "/api/executableScripts" ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for executable script creation" ) ), + fields.withPath( "script" ).description( "The reference to the script resource that is executed by this resource definition." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The name of the executable script." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The code of the executable script." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description of the purpose of the executable script." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "overrideArguments" ).description( "The overridden arguments of the script resource. If the script resource defined mandatory arguments with null values, these must be specified for the executable script. " + + "Otherwise the script cannot be executed. If no argument value should be overridden, this field need not to be specified." ).type( JsonFieldType.ARRAY ).optional(), + fields.withPath( "overrideArguments[].argument" ).description( "Reference to the script argument resource for which the value should be overridden." ).type( JsonFieldType.STRING ), + fields.withPath( "overrideArguments[].overrideValue" ).description( "The value that should be used for the argument when executing the script. The value must match the data type of the argument." ).type( JsonFieldType.STRING ), + fields.withPath( "overrideArguments[].enabled" ).description( "Specifies if the override argument is enabled. If the override argument is not enabled, the value of the script argument itself is used." ).type( JsonFieldType.BOOLEAN ) + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "name", is( "CP: Birth Weight" ) ) ) + .andExpect( jsonPath( "code", is( "CP_BIRTH_WEIGHT" ) ) ) + .andExpect( jsonPath( "overrideArguments.length()", is( 2 ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readExecutableScript() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( ExecutableScript.class, constraintDescriptionResolver ); + final String executableScriptId = loadExecutableScript( "CP_OPV_DOSE" ).getId().toString(); + docMockMvc.perform( get( "/api/executableScripts/{executableScriptId}", executableScriptId ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "executableScript" ).description( "Link to this resource itself." ), + linkWithRel( "script" ).description( "Link to the script to which the resource belongs to." ) ), responseFields( + attributes( key( "title" ).value( "Fields for executable script reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The name of the executable script." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The code of the executable script." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description of the purpose of the executable script." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "overrideArguments" ).description( "The overridden arguments of the script resource. If the script resource defined mandatory arguments with null values, these must be specified for the executable script. " + + "Otherwise the script cannot be executed. If no argument value should be overridden, this field need not to be specified." ).type( JsonFieldType.ARRAY ).optional(), + fields.withPath( "overrideArguments[].overrideValue" ).description( "The value that should be used for the argument when executing the script. The value must match the data type of the argument." ).type( JsonFieldType.STRING ), + fields.withPath( "overrideArguments[].enabled" ).description( "Specifies if the override argument is enabled. If the override argument is not enabled, the value of the script argument itself is used." ).type( JsonFieldType.BOOLEAN ), + subsectionWithPath( "_links" ).description( "Links to other resources" ), + subsectionWithPath( "overrideArguments[]._links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected Script loadScript( @Nonnull String code ) + { + final Script example = new Script(); + example.setCode( code ); + return scriptRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Script does not exist: " + code ) ); + } + + @Nonnull + protected ScriptArg loadScriptArg( @Nonnull Script script, @Nonnull String name ) + { + final Script exampleScript = new Script(); + exampleScript.setId( script.getId() ); + final ScriptArg example = new ScriptArg(); + example.setScript( exampleScript ); + example.setName( name ); + return scriptArgRepository.findOne( Example.of( example, ExampleMatcher.matching().withIgnorePaths( "arrayValue", "mandatory" ) ) ) + .orElseThrow( () -> new AssertionError( "Script argument does not exist: " + name ) ); + } + + @Nonnull + protected ExecutableScript loadExecutableScript( @Nonnull String code ) + { + final ExecutableScript executableScript = new ExecutableScript(); + executableScript.setCode( code ); + return executableScriptRepository.findOne( Example.of( executableScript ) ).orElseThrow( () -> new AssertionError( "Executable script does not exist: " + code ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionRepositoryRestDocsTest.java new file mode 100644 index 00000000..39e21188 --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionRepositoryRestDocsTest.java @@ -0,0 +1,197 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.RemoteSubscription; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link RemoteSubscriptionRepository}. + * + * @author volsch + */ +public class RemoteSubscriptionRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private RemoteSubscriptionRepository remoteSubscriptionRepository; + + @Test + public void createRemoteSubscription() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( RemoteSubscription.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscription.json", StandardCharsets.UTF_8 ); + final String location = docMockMvc.perform( post( "/api/remoteSubscriptions" ).header( AUTHORIZATION_HEADER_NAME, ADMINISTRATION_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for remote subscription creation" ) ), + fields.withPath( "name" ).description( "The name of the remote subscription." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The code of the remote subscription." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description of the purpose of the remote subscription." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "enabled" ).description( "Specifies if this remote subscription has been enabled. " + + "If the remote subscription has not been enabled, no subscription notifications are processed from the corresponding FHIR service." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "locked" ).description( "Specifies if this remote subscription has been locked. " + + "If the remote subscription has been locked (i.e. by automatic processes), no subscription notifications are processed from the corresponding FHIR service." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "fhirVersion" ).description( "The FHIR version that should be used when communicating with the remote FHIR service." ).type( JsonFieldType.STRING ), + fields.withPath( "toleranceMillis" ).description( "The number of milli-seconds to subtract from the last updated timestamp when searching for created and updated resources." ).type( JsonFieldType.NUMBER ), + fields.withPath( "autoCreatedSubscriptionResources" ).description( "Subscription resources for which the subscriptions should be created automatically when creating the subscription resource. This value will not be returned and can only " + + "be used when creating and updating the entity." ).type( JsonFieldType.ARRAY ).optional(), + fields.withPath( "adapterEndpoint" ).description( "Specifies remote subscription settings that are relevant for the adapter." ).type( JsonFieldType.OBJECT ), + fields.withPath( "adapterEndpoint.baseUrl" ).description( "The base URL of the adapter that is used to register the subscription on the FHIR service. " + + "If the FHIR service runs on a different server, the URL must not contain localhost. If this URL is not specified it is calculated automatically." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "adapterEndpoint.subscriptionType" ).description( "The subscription type that is used to register the subscription on FHIR service. " + + "A normal REST hook subscription is sufficient (without any payload). If this causes issues on the FHIR service also a subscription with payload can be used." ) + .type( JsonFieldType.STRING ), + fields.withPath( "adapterEndpoint.authorizationHeader" ).description( "The authorization header value that is expected by the adapter when it receives a subscription notification from the FHIR service. " + + "This should include a bearer token." ).type( JsonFieldType.STRING ), + fields.withPath( "dhisEndpoint" ).description( "Specifies remote subscription settings that are relevant for the connection to DHIS2." ).type( JsonFieldType.OBJECT ), + fields.withPath( "dhisEndpoint.authenticationMethod" ).description( "The authentication method that should be used when connecting to DHIS2." ).type( JsonFieldType.STRING ), + fields.withPath( "dhisEndpoint.username" ).description( "The username that is used to connect to DHIS2 when handling data of this remote subscription." ).type( JsonFieldType.STRING ), + fields.withPath( "dhisEndpoint.password" ).description( "The password that is used to connect to DHIS2 when handling data of this remote subscription. " + + "This value will not be returned and will be set using the original value when performing an update without this value." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "fhirEndpoint" ).description( "Specifies remote subscription settings that are relevant for the connection to FHIR." ).type( JsonFieldType.OBJECT ), + fields.withPath( "fhirEndpoint.baseUrl" ).description( "The base URL of the FHIR endpoints on the FHIR service." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirEndpoint.headers" ).description( "The headers that are sent to the remote FHIR service when connecting to the FHIR endpoints." ).type( JsonFieldType.ARRAY ), + fields.withPath( "fhirEndpoint.headers[].name" ).description( "The name of the header for which the value will be sent (e.g. Authorization)." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirEndpoint.headers[].value" ).description( "The value of the header for which the value will be sent (e.g. a bearer token). If the value of the header is marked as secure, " + + "the value will not be returned and will be set using the original value when performing an update without this value." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "fhirEndpoint.headers[].secure" ).description( "Specifies if the value of the header is secure and should not be returned " + + "(e.g. when it contains authentication information)." ).type( JsonFieldType.BOOLEAN ) + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, ADMINISTRATION_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "name", is( "Main Subscription" ) ) ) + .andExpect( jsonPath( "code", is( "MAIN_SUBSCRIPTION" ) ) ) + .andExpect( jsonPath( "description", is( "Main FHIR service on which the adapter has subscriptions." ) ) ) + .andExpect( jsonPath( "enabled", is( true ) ) ) + .andExpect( jsonPath( "locked", is( false ) ) ) + .andExpect( jsonPath( "autoCreatedSubscriptionResources" ).doesNotExist() ) + .andExpect( jsonPath( "adapterEndpoint.baseUrl", is( "http://localhist:8081" ) ) ) + .andExpect( jsonPath( "adapterEndpoint.subscriptionType", is( "REST_HOOK" ) ) ) + .andExpect( jsonPath( "adapterEndpoint.authorizationHeader", is( "Bearer 98a7558102b7bdc4da5c8f74ca63958c498b4bd9231bd3b0cc" ) ) ) + .andExpect( jsonPath( "dhisEndpoint.authenticationMethod", is( "BASIC" ) ) ) + .andExpect( jsonPath( "dhisEndpoint.username", is( "admin" ) ) ) + .andExpect( jsonPath( "dhisEndpoint.password" ).doesNotExist() ) + .andExpect( jsonPath( "fhirEndpoint.baseUrl", is( "http://localhost:8082/hapi-fhir-jpaserver-example/baseDstu3" ) ) ) + .andExpect( jsonPath( "fhirEndpoint.headers[0].name", is( "Authorization" ) ) ) + .andExpect( jsonPath( "fhirEndpoint.headers[0].value" ).doesNotExist() ) + .andExpect( jsonPath( "fhirEndpoint.headers[0].secure", is( true ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readRemoteSubscription() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( RemoteSubscription.class, constraintDescriptionResolver ); + final String remoteSubscriptionId = loadRemoteSubscription( "DEFAULT_SUBSCRIPTION" ).getId().toString(); + docMockMvc.perform( get( "/api/remoteSubscriptions/{remoteSubscriptionId}", remoteSubscriptionId ).header( AUTHORIZATION_HEADER_NAME, ADMINISTRATION_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "remoteSubscription" ).description( "Link to this resource itself." ), + linkWithRel( "systems" ).description( "Link to the system URI resources that belong to this remote subscription." ), + linkWithRel( "resources" ).description( "Link to the subscribed FHIR resources that belong to this remote subscription." ) ), responseFields( + attributes( key( "title" ).value( "Fields for remote subscription reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The name of the remote subscription." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The code of the remote subscription." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description of the purpose of the remote subscription." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "enabled" ).description( "Specifies if this remote subscription has been enabled. " + + "If the remote subscription has not been enabled, no subscription notifications are processed from the corresponding FHIR service." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "locked" ).description( "Specifies if this remote subscription has been locked. " + + "If the remote subscription has been locked (i.e. by automatic processes), no subscription notifications are processed from the corresponding FHIR service." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "fhirVersion" ).description( "The FHIR version that should be used when communicating with the remote FHIR service." ).type( JsonFieldType.STRING ), + fields.withPath( "toleranceMillis" ).description( "The number of milli-seconds to subtract from the last updated timestamp when searching for created and updated resources." ).type( JsonFieldType.NUMBER ), + fields.withPath( "autoCreatedSubscriptionResources" ).description( "Subscription resources for which the subscriptions should be created automatically when creating the subscription resource. This value will not be returned and can only " + + "be used when creating and updating the entity." ).type( JsonFieldType.ARRAY ).optional(), + fields.withPath( "adapterEndpoint" ).description( "Specifies remote subscription settings that are relevant for the adapter." ).type( JsonFieldType.OBJECT ), + fields.withPath( "adapterEndpoint.baseUrl" ).description( "The base URL of the adapter that is used to register the subscription on the FHIR service. " + + "If the FHIR service runs on a different server, the URL must not contain localhost. If this URL is not specified it is calculated automatically." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "adapterEndpoint.subscriptionType" ).description( "The subscription type that is used to register the subscription on FHIR service. " + + "A normal REST hook subscription is sufficient (without any payload). If this causes issues on the FHIR service also a subscription with payload can be used." ) + .type( JsonFieldType.STRING ), + fields.withPath( "adapterEndpoint.authorizationHeader" ).description( "The authorization header value that is expected by the adapter when it receives a subscription notification from the FHIR service. " + + "This should include a bearer token." ).type( JsonFieldType.STRING ), + fields.withPath( "dhisEndpoint" ).description( "Specifies remote subscription settings that are relevant for the connection to DHIS2." ).type( JsonFieldType.OBJECT ), + fields.withPath( "dhisEndpoint.authenticationMethod" ).description( "The authentication method that should be used when connecting to DHIS2." ).type( JsonFieldType.STRING ), + fields.withPath( "dhisEndpoint.username" ).description( "The username that is used to connect to DHIS2 when handling data of this remote subscription." ).type( JsonFieldType.STRING ), + fields.withPath( "dhisEndpoint.password" ).description( "The password that is used to connect to DHIS2 when handling data of this remote subscription. " + + "This value will not be returned and will be set using the original value when performing an update without this value." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "fhirEndpoint" ).description( "Specifies remote subscription settings that are relevant for the connection to FHIR." ).type( JsonFieldType.OBJECT ), + fields.withPath( "fhirEndpoint.baseUrl" ).description( "The base URL of the FHIR endpoints on the FHIR service." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirEndpoint.logging" ).description( "Specifies if basic logging should be enabled when communicating with the FHIR endpoints of this FHIR service." ).type( JsonFieldType.BOOLEAN ).optional(), + fields.withPath( "fhirEndpoint.verboseLogging" ).description( "Specifies if verbose logging (includes complete pazload) should be enabled when communicating with the FHIR endpoints of this FHIR service. " + + "Enabling verbose logging may log confidential patient data. This could violate data protection laws and regulations." ).type( JsonFieldType.BOOLEAN ).optional(), + fields.withPath( "fhirEndpoint.headers" ).description( "The headers that are sent to the remote FHIR service when connecting to the FHIR endpoints." ).type( JsonFieldType.ARRAY ), + fields.withPath( "fhirEndpoint.headers[].name" ).description( "The name of the header for which the value will be sent (e.g. Authorization)." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirEndpoint.headers[].value" ).description( "The value of the header for which the value will be sent (e.g. a bearer token). If the value of the header is marked as secure, " + + "the value will not be returned and will be set using the original value when performing an update without this value." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "fhirEndpoint.headers[].secure" ).description( "Specifies if the value of the header is secure and should not be returned " + + "(e.g. when it contains authentication information)." ).type( JsonFieldType.BOOLEAN ), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected RemoteSubscription loadRemoteSubscription( @Nonnull String code ) + { + final RemoteSubscription remoteSubscription = new RemoteSubscription(); + remoteSubscription.setCode( code ); + return remoteSubscriptionRepository.findOne( Example.of( remoteSubscription, + ExampleMatcher.matching().withIgnorePaths( "toleranceMillis", "logging", "verboseLogging", "enabled", "locked" ) ) ).orElseThrow( () -> new AssertionError( "Remote subscription does not exist: " + code ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionResourceRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionResourceRepositoryRestDocsTest.java new file mode 100644 index 00000000..3caabbd0 --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionResourceRepositoryRestDocsTest.java @@ -0,0 +1,141 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.RemoteSubscription; +import org.dhis2.fhir.adapter.fhir.metadata.model.RemoteSubscriptionResource; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.UUID; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link RemoteSubscriptionResourceRepository}. + * + * @author volsch + */ +public class RemoteSubscriptionResourceRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private SystemRepository systemRepository; + + @Autowired + private RemoteSubscriptionRepository remoteSubscriptionRepository; + + @Autowired + private RemoteSubscriptionResourceRepository remoteSubscriptionResourceRepository; + + @Test + public void createRemoteSubscriptionResource() throws Exception + { + final String remoteSubscriptionId = loadRemoteSubscription( "DEFAULT_SUBSCRIPTION" ).getId().toString(); + final ConstrainedFields fields = new ConstrainedFields( RemoteSubscriptionResource.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionResource.json", StandardCharsets.UTF_8 ) + .replace( "$remoteSubscriptionId", API_BASE_URI + "/remoteSubscriptions/" + remoteSubscriptionId ); + final String location = docMockMvc.perform( post( "/api/remoteSubscriptionResources" ).header( AUTHORIZATION_HEADER_NAME, ADMINISTRATION_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for script source creation" ) ), + fields.withPath( "remoteSubscription" ).description( "The reference to the remote subscription to which this resource belongs to." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirResourceType" ).description( "The type of the subscribed FHIR resource." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description of the purpose of the subscribed FHIR resource." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "fhirCriteriaParameters" ).description( "The prefix that should be added to the codes when mapping them to DHIS2." ).type( JsonFieldType.STRING ).optional() + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, ADMINISTRATION_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "fhirResourceType", is( "IMMUNIZATION" ) ) ) + .andExpect( jsonPath( "description", is( "Subscription for all immunizations." ) ) ) + .andExpect( jsonPath( "fhirCriteriaParameters" ).doesNotExist() ) + .andExpect( jsonPath( "fhirSubscriptionId" ).doesNotExist() ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readRemoteSubscriptionResource() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( RemoteSubscriptionResource.class, constraintDescriptionResolver ); + final String remoteSubscriptionResourceId = loadRemoteSubscriptionResource( "667bfa41-867c-4796-86b6-eb9f9ed4dc94" ).getId().toString(); + docMockMvc.perform( get( "/api/remoteSubscriptionResources/{remoteSubscriptionResourceId}", remoteSubscriptionResourceId ).header( AUTHORIZATION_HEADER_NAME, ADMINISTRATION_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "remoteSubscriptionResource" ).description( "Link to this resource itself." ), + linkWithRel( "remoteSubscription" ).description( "The reference to the remote subscription to which this resource belongs to." ) ), responseFields( + attributes( key( "title" ).value( "Fields for remote subscription resource reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirResourceType" ).description( "The type of the subscribed FHIR resource." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description of the purpose of the subscribed FHIR resource." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "fhirCriteriaParameters" ).description( "The prefix that should be added to the codes when mapping them to DHIS2." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "hirSubscriptionId" ).description( "The ID of the automatically created FHIR subscription on the FHIR service." ).type( JsonFieldType.STRING ).optional(), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected RemoteSubscription loadRemoteSubscription( @Nonnull String code ) + { + final RemoteSubscription remoteSubscription = new RemoteSubscription(); + remoteSubscription.setCode( code ); + return remoteSubscriptionRepository.findOne( Example.of( remoteSubscription, + ExampleMatcher.matching().withIgnorePaths( "toleranceMillis", "logging", "verboseLogging", "enabled", "locked" ) ) ).orElseThrow( () -> new AssertionError( "Remote subscription does not exist: " + code ) ); + } + + @Nonnull + protected RemoteSubscriptionResource loadRemoteSubscriptionResource( @Nonnull String id ) + { + return remoteSubscriptionResourceRepository.findById( UUID.fromString( id ) ).orElseThrow( () -> new AssertionError( "Remote subscription resource does not exist: " + id ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionSystemRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionSystemRepositoryRestDocsTest.java new file mode 100644 index 00000000..9c626eda --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RemoteSubscriptionSystemRepositoryRestDocsTest.java @@ -0,0 +1,149 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +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.System; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.UUID; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link RemoteSubscriptionSystem}. + * + * @author volsch + */ +public class RemoteSubscriptionSystemRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private SystemRepository systemRepository; + + @Autowired + private RemoteSubscriptionRepository remoteSubscriptionRepository; + + @Autowired + private RemoteSubscriptionSystemRepository remoteSubscriptionSystemRepository; + + @Test + public void createRemoteSubscriptionSystem() throws Exception + { + final String systemId = loadSystem( "SYSTEM_SL_LOCATION" ).getId().toString(); + final String remoteSubscriptionId = loadRemoteSubscription( "DEFAULT_SUBSCRIPTION" ).getId().toString(); + final ConstrainedFields fields = new ConstrainedFields( RemoteSubscriptionSystem.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionSystem.json", StandardCharsets.UTF_8 ) + .replace( "$systemId", API_BASE_URI + "/systems/" + systemId ).replace( "$remoteSubscriptionId", API_BASE_URI + "/remoteSubscriptions/" + remoteSubscriptionId ); + final String location = docMockMvc.perform( post( "/api/remoteSubscriptionSystems" ).header( AUTHORIZATION_HEADER_NAME, ADMINISTRATION_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for script source creation" ) ), + fields.withPath( "remoteSubscription" ).description( "The reference to the remote subscription to which this resource belongs to." ).type( JsonFieldType.STRING ), + fields.withPath( "system" ).description( "The reference to the system URI that should be mapped to the remote subscription." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirResourceType" ).description( "The FHIR resource type to which the system URI should be mapped." ).type( JsonFieldType.STRING ), + fields.withPath( "codePrefix" ).description( "The prefix that should be added to the codes when mapping them to DHIS2." ).type( JsonFieldType.STRING ).optional() + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, ADMINISTRATION_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "fhirResourceType", is( "LOCATION" ) ) ) + .andExpect( jsonPath( "codePrefix", is( "LOC_" ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readRemoteSubscriptionSystem() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( RemoteSubscriptionSystem.class, constraintDescriptionResolver ); + final String remoteSubscriptionSystemId = loadRemoteSubscriptionSystem( "ea9804a3-9e82-4d0d-9cd2-e417b32b1c0c" ).getId().toString(); + docMockMvc.perform( get( "/api/remoteSubscriptionSystems/{remoteSubscriptionSystemId}", remoteSubscriptionSystemId ).header( AUTHORIZATION_HEADER_NAME, ADMINISTRATION_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "remoteSubscriptionSystem" ).description( "Link to this resource itself." ), + linkWithRel( "remoteSubscription" ).description( "The reference to the remote subscription to which this resource belongs to." ), + linkWithRel( "system" ).description( "The reference to the system URI that should be mapped to the remote subscription." ) ), responseFields( + attributes( key( "title" ).value( "Fields for remote subscription system reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirResourceType" ).description( "The FHIR resource type to which the system URI should be mapped." ).type( JsonFieldType.STRING ), + fields.withPath( "codePrefix" ).description( "The prefix that should be added to the codes when mapping them to DHIS2." ).type( JsonFieldType.STRING ).optional(), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected System loadSystem( @Nonnull String code ) + { + final System system = new System(); + system.setCode( code ); + return systemRepository.findOne( Example.of( system, ExampleMatcher.matching().withIgnorePaths( "enabled" ) ) ) + .orElseThrow( () -> new AssertionError( "System does not exist: " + code ) ); + } + + @Nonnull + protected RemoteSubscription loadRemoteSubscription( @Nonnull String code ) + { + final RemoteSubscription remoteSubscription = new RemoteSubscription(); + remoteSubscription.setCode( code ); + return remoteSubscriptionRepository.findOne( Example.of( remoteSubscription, + ExampleMatcher.matching().withIgnorePaths( "toleranceMillis", "logging", "verboseLogging", "enabled", "locked" ) ) ).orElseThrow( () -> new AssertionError( "Remote subscription does not exist: " + code ) ); + } + + @Nonnull + protected RemoteSubscriptionSystem loadRemoteSubscriptionSystem( @Nonnull String id ) + { + return remoteSubscriptionSystemRepository.findById( UUID.fromString( id ) ).orElseThrow( () -> new AssertionError( "Remote subscription system does not exist: " + id ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RuleRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RuleRepositoryRestDocsTest.java new file mode 100644 index 00000000..c0313c5c --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/RuleRepositoryRestDocsTest.java @@ -0,0 +1,176 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.AbstractRule; +import org.dhis2.fhir.adapter.fhir.metadata.model.ExecutableScript; +import org.dhis2.fhir.adapter.fhir.metadata.model.TrackedEntityRule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link RuleRepository}. + * + * @author volsch + */ +public class RuleRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private RuleRepository ruleRepository; + + @Autowired + private ExecutableScriptRepository executableScriptRepository; + + @Test + public void createTrackedEntityRule() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( TrackedEntityRule.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createTrackedEntityRule.json", StandardCharsets.UTF_8 ) + .replace( "$transformInScriptId", API_BASE_URI + "/executableScripts/" + loadScript( "TRANSFORM_FHIR_PATIENT_DHIS_PERSON" ).getId().toString() ) + .replace( "$orgUnitLookupScriptId", API_BASE_URI + "/executableScripts/" + loadScript( "EXTRACT_FHIR_PATIENT_DHIS_ORG_UNIT_CODE" ).getId().toString() ) + .replace( "$locationLookupScriptId", API_BASE_URI + "/executableScripts/" + loadScript( "EXTRACT_FHIR_PATIENT_GEO_LOCATION" ).getId().toString() ); + final String location = docMockMvc.perform( post( "/api/rules" ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for rule creation" ) ), + fields.withPath( "name" ).description( "The unique name of the rule." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the rule is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "dhisResourceType" ).description( "The type of the rule and the type of the data that is stored in DHIS2." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirResourceType" ).description( "The FHIR resource type of the incoming resource." ).type( JsonFieldType.STRING ), + fields.withPath( "enabled" ).description( "Specifies if this rule is enabled." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "evaluationOrder" ).description( "Specifies the precedence of this rule when several rules match. Higher values define a higher precedence." ).type( JsonFieldType.NUMBER ), + fields.withPath( "applicableCodeSet" ).description( "Link to the code set reference that is used to check if the incoming request is applicable for this rule." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "applicableInScript" ).description( "Link to the executable script reference that is used to check if the incoming request is applicable for this rule. " + + "The script must be an evaluation script that returns a boolean value." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "transformInScript" ).description( "Link to the executable script reference that is used to transform the FHIR resource input to the DHIS2 resource." ).type( JsonFieldType.STRING ), + fields.withPath( "orgUnitLookupScript" ).description( "Link to the executable script reference that is used to extract the organization unit reference. " + + "The script must be an evaluation script that return a reference." ).type( JsonFieldType.STRING ), + fields.withPath( "locationLookupScript" ).description( "Link to the executable script reference that is used to extract the location reference. " + + "The script must be an evaluation script that return a location (geo coordinates)." ).type( JsonFieldType.STRING ), + fields.withPath( "trackedEntityReference" ).description( "The reference to the DHIS2 Tracked Entity Type." ).type( JsonFieldType.OBJECT ), + fields.withPath( "trackedEntityReference.value" ).description( "The unique ID/code/name of the Tracked Entity Type." ).type( JsonFieldType.STRING ), + fields.withPath( "trackedEntityReference.type" ).description( "The type of reference value of the Tracked Entity Type." ).type( JsonFieldType.STRING ), + fields.withPath( "trackedEntityIdentifierReference" ).description( "The reference to the DHIS2 Tracked Entity Attribute that is used as national identifier." ).type( JsonFieldType.OBJECT ), + fields.withPath( "trackedEntityIdentifierReference.value" ).description( "The unique ID/code/name of the Tracked Entity Attribute." ).type( JsonFieldType.STRING ), + fields.withPath( "trackedEntityIdentifierReference.type" ).description( "The type of reference value of the Tracked Entity Attribute." ).type( JsonFieldType.STRING ) + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "name", is( "FHIR Patient to Person (disabled)" ) ) ) + .andExpect( jsonPath( "enabled", is( false ) ) ) + .andExpect( jsonPath( "evaluationOrder", is( 0 ) ) ) + .andExpect( jsonPath( "dhisResourceType", is( "TRACKED_ENTITY" ) ) ) + .andExpect( jsonPath( "fhirResourceType", is( "PATIENT" ) ) ) + .andExpect( jsonPath( "trackedEntityReference.value", is( "Person" ) ) ) + .andExpect( jsonPath( "trackedEntityReference.type", is( "NAME" ) ) ) + .andExpect( jsonPath( "trackedEntityIdentifierReference.value", is( "National identifier" ) ) ) + .andExpect( jsonPath( "trackedEntityIdentifierReference.type", is( "CODE" ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readTrackedEntityRule() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( TrackedEntityRule.class, constraintDescriptionResolver ); + final String ruleId = loadTrackedEntityRule( "FHIR Patient to Person" ).getId().toString(); + docMockMvc.perform( get( "/api/rules/{ruleId}", ruleId ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "trackedEntityRule" ).description( "Link to this resource itself." ), + linkWithRel( "applicableCodeSet" ).description( "Link to the code set reference that is used to check if the incoming request is applicable for this rule." ).optional(), + linkWithRel( "applicableInScript" ).description( "Link to the executable script reference that is used to check if the incoming request is applicable for this rule. The script must be an evaluation script that returns a boolean value." ).optional(), + linkWithRel( "transformInScript" ).description( "Link to the executable script reference that is used to transform the FHIR resource input to the DHIS2 resource." ), + linkWithRel( "orgUnitLookupScript" ).description( "Link to the executable script reference that is used to extract the organization unit reference. The script must be an evaluation script that return a reference." ), + linkWithRel( "locationLookupScript" ).description( "Link to the executable script reference that is used to extract the location reference. The script must be an evaluation script that return a location (geo coordinates)." ) ), + responseFields( + attributes( key( "title" ).value( "Fields for rule reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The unique name of the rule." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the rule is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "dhisResourceType" ).description( "The type of the rule and the type of the data that is stored in DHIS2." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirResourceType" ).description( "The FHIR resource type of the incoming resource." ).type( JsonFieldType.STRING ), + fields.withPath( "enabled" ).description( "Specifies if this rule is enabled." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "evaluationOrder" ).description( "Specifies the precedence of this rule when several rules match. Higher values define a higher precedence." ).type( JsonFieldType.NUMBER ), + fields.withPath( "trackedEntityReference" ).description( "The reference to the DHIS2 Tracked Entity Type." ).type( JsonFieldType.OBJECT ), + fields.withPath( "trackedEntityReference.value" ).description( "The unique ID/code/name of the Tracked Entity Type." ).type( JsonFieldType.STRING ), + fields.withPath( "trackedEntityReference.type" ).description( "The type of reference value of the Tracked Entity Type." ).type( JsonFieldType.STRING ), + fields.withPath( "trackedEntityIdentifierReference" ).description( "The reference to the DHIS2 Tracked Entity Attribute that is used as national identifier." ).type( JsonFieldType.OBJECT ), + fields.withPath( "trackedEntityIdentifierReference.value" ).description( "The unique ID/code/name of the Tracked Entity Attribute." ).type( JsonFieldType.STRING ), + fields.withPath( "trackedEntityIdentifierReference.type" ).description( "The type of reference value of the Tracked Entity Attribute." ).type( JsonFieldType.STRING ), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected AbstractRule loadTrackedEntityRule( @Nonnull String name ) + { + final AbstractRule example = new TrackedEntityRule(); + example.setName( name ); + return ruleRepository.findOne( Example.of( example, ExampleMatcher.matching().withIgnorePaths( "enabled", "evaluationOrder" ) ) ) + .orElseThrow( () -> new AssertionError( "Rule does not exist: " + name ) ); + } + + @Nonnull + protected ExecutableScript loadScript( @Nonnull String code ) + { + final ExecutableScript example = new ExecutableScript(); + example.setCode( code ); + return executableScriptRepository.findOne( Example.of( example ) ) + .orElseThrow( () -> new AssertionError( "Executable script does not exist: " + code ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptArgRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptArgRepositoryRestDocsTest.java new file mode 100644 index 00000000..f97e5f3a --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptArgRepositoryRestDocsTest.java @@ -0,0 +1,144 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.Script; +import org.dhis2.fhir.adapter.fhir.metadata.model.ScriptArg; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.UUID; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link ScriptArg}. + * + * @author volsch + */ +public class ScriptArgRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private ScriptRepository scriptRepository; + + @Autowired + private ScriptArgRepository scriptArgRepository; + + @Test + public void createScriptArg() throws Exception + { + final String scriptId = loadScript( "TRANSFORM_FHIR_OB_BODY_WEIGHT" ).getId().toString(); + final ConstrainedFields fields = new ConstrainedFields( ScriptArg.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptArg.json", StandardCharsets.UTF_8 ) + .replace( "$scriptId", API_BASE_URI + "/script/" + scriptId ); + final String location = docMockMvc.perform( post( "/api/scriptArgs" ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for script argument creation" ) ), + fields.withPath( "script" ).description( "The reference to the script resource to which this argument belongs to." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The name of the script argument. This is also used inside the script source to access the argument value." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description of the purpose of the argument." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "dataType" ).description( "The data type of the argument value." ).type( JsonFieldType.STRING ), + fields.withPath( "array" ).description( "Specifies if the argument contains an array of values. The values must be separated by a pipe character." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "mandatory" ).description( "Specifies if the argument is mandatory and cannot be null when the script is executed." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "defaultValue" ).description( "The default value of the argument. This may be overridden by the executable script. The value must be convertible to the specified data type. " + + "If the argument is an array, the array values must be separated by pipe characters." ).type( JsonFieldType.STRING ).optional() + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "name", is( "weightUnit" ) ) ) + .andExpect( jsonPath( "description", is( "The resulting weight unit in which the value will be set on the data element." ) ) ) + .andExpect( jsonPath( "dataType", is( "WEIGHT_UNIT" ) ) ) + .andExpect( jsonPath( "array", is( false ) ) ) + .andExpect( jsonPath( "mandatory", is( true ) ) ) + .andExpect( jsonPath( "defaultValue", is( "KILO_GRAM" ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readScriptArg() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( ScriptArg.class, constraintDescriptionResolver ); + final String scriptArgId = loadScriptArg( "d8cd0e7d-7780-45d1-8094-b448b480e6b8" ).getId().toString(); + docMockMvc.perform( get( "/api/scriptArgs/{scriptArgId}", scriptArgId ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "scriptArg" ).description( "Link to this resource itself." ), + linkWithRel( "script" ).description( "Link to the script to which the resource belongs to." ) ), responseFields( + attributes( key( "title" ).value( "Fields for script argument reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The name of the script argument. This is also used inside the script source to access the argument value." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description of the purpose of the argument." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "dataType" ).description( "The data type of the argument value." ).type( JsonFieldType.STRING ), + fields.withPath( "array" ).description( "Specifies if the argument contains an array of values. The values must be separated by a pipe character." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "mandatory" ).description( "Specifies if the argument is mandatory and cannot be null when the script is executed." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "defaultValue" ).description( "Specifies if the argument is mandatory and cannot be null when the script is executed." ).type( JsonFieldType.STRING ).optional(), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected Script loadScript( @Nonnull String code ) + { + final Script example = new Script(); + example.setCode( code ); + return scriptRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Script does not exist: " + code ) ); + } + + @Nonnull + protected ScriptArg loadScriptArg( @Nonnull String scriptId ) + { + return scriptArgRepository.findById( UUID.fromString( scriptId ) ).orElseThrow( () -> new AssertionError( "Script source does not exist: " + scriptId ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptRepositoryRestDocsTest.java new file mode 100644 index 00000000..e423a25c --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptRepositoryRestDocsTest.java @@ -0,0 +1,137 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.Script; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link ScriptRepository}. + * + * @author volsch + */ +public class ScriptRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private ScriptRepository scriptRepository; + + @Test + public void createScript() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( Script.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createScript.json", StandardCharsets.UTF_8 ); + final String location = docMockMvc.perform( post( "/api/scripts" ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for script creation" ) ), + fields.withPath( "name" ).description( "The unique name of the script." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the script." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the script is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "scriptType" ).description( "The the of the script that describes its purpose." ).type( JsonFieldType.STRING ), + fields.withPath( "returnType" ).description( "The data type of the value that is returned by the script." ).type( JsonFieldType.STRING ), + fields.withPath( "inputType" ).description( "The required data type of the transformation input." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "outputType" ).description( "The required data type of the transformation output." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "variables" ).description( "The variables that are required for the script execution." ).type( JsonFieldType.ARRAY ).optional() + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "name", is( "Transforms FHIR Immunization to Y/N data element" ) ) ) + .andExpect( jsonPath( "code", is( "TRANSFORM_FHIR_IMMUNIZATION_YN" ) ) ) + .andExpect( jsonPath( "description", is( "Transforms FHIR Immunization to Y/N data element." ) ) ) + .andExpect( jsonPath( "scriptType", is( "TRANSFORM_TO_DHIS" ) ) ) + .andExpect( jsonPath( "returnType", is( "BOOLEAN" ) ) ) + .andExpect( jsonPath( "inputType", is( "FHIR_IMMUNIZATION" ) ) ) + .andExpect( jsonPath( "outputType", is( "DHIS_EVENT" ) ) ) + .andExpect( jsonPath( "variables", Matchers.contains( "CONTEXT", "INPUT", "OUTPUT" ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readScript() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( Script.class, constraintDescriptionResolver ); + final String scriptId = loadScript( "TRANSFORM_FHIR_IMMUNIZATION_OS" ).getId().toString(); + docMockMvc.perform( get( "/api/scripts/{scriptId}", scriptId ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "script" ).description( "Link to this resource itself." ), + linkWithRel( "arguments" ).description( "Link to the arguments that are provided as variable args (contains a map with all argument values)." ), + linkWithRel( "sources" ).description( "Link to the source codes of the scripts (multiple scripts for different FHIR versions)." ) ), responseFields( + attributes( key( "title" ).value( "Fields for script reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The unique name of the script." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the script." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the script is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "scriptType" ).description( "The the of the script that describes its purpose." ).type( JsonFieldType.STRING ), + fields.withPath( "returnType" ).description( "The data type of the value that is returned by the script." ).type( JsonFieldType.STRING ), + fields.withPath( "inputType" ).description( "The required data type of the transformation input." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "outputType" ).description( "The required data type of the transformation output." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "variables" ).description( "The variables that are required for the script execution." ).type( JsonFieldType.ARRAY ).optional(), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected Script loadScript( @Nonnull String code ) + { + final Script example = new Script(); + example.setCode( code ); + return scriptRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Script does not exist: " + code ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptSourceRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptSourceRepositoryRestDocsTest.java new file mode 100644 index 00000000..5c09c4b1 --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/ScriptSourceRepositoryRestDocsTest.java @@ -0,0 +1,134 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.Script; +import org.dhis2.fhir.adapter.fhir.metadata.model.ScriptSource; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.UUID; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link ScriptSourceRepository}. + * + * @author volsch + */ +public class ScriptSourceRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private ScriptRepository scriptRepository; + + @Autowired + private ScriptSourceRepository scriptSourceRepository; + + @Test + public void createScriptSource() throws Exception + { + final String scriptId = loadScript( "TRANSFORM_FHIR_OB_BODY_WEIGHT" ).getId().toString(); + final ConstrainedFields fields = new ConstrainedFields( ScriptSource.class, constraintDescriptionResolver ); + final String request = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptSource.json", StandardCharsets.UTF_8 ).replace( "$scriptId", API_BASE_URI + "/script/" + scriptId ); + final String location = docMockMvc.perform( post( "/api/scriptSources" ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( request ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for script source creation" ) ), + fields.withPath( "script" ).description( "The reference to the script to which this source belongs to." ).type( JsonFieldType.STRING ), + fields.withPath( "sourceType" ).description( "The type of the source code (the programming language)." ).type( JsonFieldType.STRING ), + fields.withPath( "sourceText" ).description( "The code of the script in the configured programming language (source type)." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "fhirVersions" ).description( "The FHIR versions that are supported by this script source." ).type( JsonFieldType.ARRAY ) + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "sourceText", is( "output.setValue(args['dataElement'], vitalSignUtils.getWeight(input.value, args['weightUnit'], args['round']), null, args['override'], context.getFhirRequest().getLastUpdated())" ) ) ) + .andExpect( jsonPath( "sourceType", is( "JAVASCRIPT" ) ) ) + .andExpect( jsonPath( "fhirVersions", Matchers.contains( "DSTU3" ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readScriptSource() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( ScriptSource.class, constraintDescriptionResolver ); + final String scriptSourceId = loadScriptSource( "081c4642-bb83-44ab-b90f-aa206ad347aa" ).getId().toString(); + docMockMvc.perform( get( "/api/scriptSources/{scriptSourceId}", scriptSourceId ).header( AUTHORIZATION_HEADER_NAME, DATA_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "scriptSource" ).description( "Link to this resource itself." ), + linkWithRel( "script" ).description( "Link to the script to which the resource belongs to." ) ), responseFields( + attributes( key( "title" ).value( "Fields for script source reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "sourceType" ).description( "The type of the source code (the programming language)." ).type( JsonFieldType.STRING ), + fields.withPath( "sourceText" ).description( "The code of the script in the configured programming language (source type)." ).type( JsonFieldType.STRING ), + fields.withPath( "fhirVersions" ).description( "The FHIR versions that are supported by this script source." ).type( JsonFieldType.ARRAY ), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected Script loadScript( @Nonnull String code ) + { + final Script example = new Script(); + example.setCode( code ); + return scriptRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Script does not exist: " + code ) ); + } + + @Nonnull + protected ScriptSource loadScriptSource( @Nonnull String scriptId ) + { + return scriptSourceRepository.findById( UUID.fromString( scriptId ) ).orElseThrow( () -> new AssertionError( "Script source does not exist: " + scriptId ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/SystemCodeRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/SystemCodeRepositoryRestDocsTest.java new file mode 100644 index 00000000..18d2808b --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/SystemCodeRepositoryRestDocsTest.java @@ -0,0 +1,145 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.Code; +import org.dhis2.fhir.adapter.fhir.metadata.model.System; +import org.dhis2.fhir.adapter.fhir.metadata.model.SystemCode; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.UUID; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link SystemCodeRepository}. + * + * @author volsch + */ +public class SystemCodeRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private CodeRepository codeRepository; + + @Autowired + private SystemRepository systemRepository; + + @Autowired + private SystemCodeRepository systemCodeRepository; + + @Test + public void createSystemCode() throws Exception + { + final String systemId = loadSystem( "SYSTEM_SL_ORGANIZATION" ).getId().toString(); + final String codeId = loadCode( "OU_FT_CH" ).getId().toString(); + final ConstrainedFields fields = new ConstrainedFields( SystemCode.class, constraintDescriptionResolver ); + final String json = IOUtils.resourceToString( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystemCode.json", StandardCharsets.UTF_8 ) + .replace( "$systemId", API_BASE_URI + "/systems/" + systemId ).replace( "$codeId", API_BASE_URI + "/codes/" + codeId ); + final String location = docMockMvc.perform( post( "/api/systemCodes" ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( json ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for system code creation" ) ), + fields.withPath( "system" ).description( "The reference to the system to which the code belongs to." ).type( JsonFieldType.STRING ), + fields.withPath( "systemCode" ).description( "The code of the system." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The reference to the internal code that is used for the system specific code." ).type( JsonFieldType.STRING ) + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "systemCode", is( "982783729" ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readSystemCode() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( SystemCode.class, constraintDescriptionResolver ); + final String systemCodeId = loadSystemCode( "c513935c-9cd2-4357-a679-60f0c79bfacb" ).getId().toString(); + docMockMvc.perform( get( "/api/systemCodes/{systemCodeId}", systemCodeId ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "systemCode" ).description( "Link to this resource itself." ), + linkWithRel( "system" ).description( "Link to the system resource to which the code belongs to." ), + linkWithRel( "code" ).description( "Link to the internal code that is mapped to the system specific code." ) ), responseFields( + attributes( key( "title" ).value( "Fields for system code reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "systemCode" ).description( "The code of the system." ).type( JsonFieldType.STRING ), + fields.withPath( "systemCodeValue" ).description( "The combination of system URI and code separated by a pipe character (generated, cannot be updated)." ).type( JsonFieldType.STRING ), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected System loadSystem( @Nonnull String code ) + { + final System example = new System(); + example.setCode( code ); + return systemRepository.findOne( Example.of( example, ExampleMatcher.matching().withIgnorePaths( "enabled" ) ) ).orElseThrow( () -> new AssertionError( "System does not exist: " + code ) ); + } + + @Nonnull + protected Code loadCode( @Nonnull String code ) + { + final Code example = new Code(); + example.setCode( code ); + return codeRepository.findOne( Example.of( example ) ).orElseThrow( () -> new AssertionError( "Code does not exist: " + code ) ); + } + + @Nonnull + protected SystemCode loadSystemCode( @Nonnull String id ) + { + return systemCodeRepository.findById( UUID.fromString( id ) ).orElseThrow( () -> new AssertionError( "System code does not exist: " + id ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/SystemRepositoryRestDocsTest.java b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/SystemRepositoryRestDocsTest.java new file mode 100644 index 00000000..ca1d87ef --- /dev/null +++ b/fhir/src/test/java/org/dhis2/fhir/adapter/fhir/metadata/repository/SystemRepositoryRestDocsTest.java @@ -0,0 +1,127 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository; + +/* + * 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.io.IOUtils; +import org.dhis2.fhir.adapter.fhir.AbstractJpaRepositoryRestDocsTest; +import org.dhis2.fhir.adapter.fhir.ConstrainedFields; +import org.dhis2.fhir.adapter.fhir.metadata.model.System; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +import javax.annotation.Nonnull; +import java.util.Objects; + +import static org.hamcrest.Matchers.is; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests for {@link SystemRepository}. + * + * @author volsch + */ +public class SystemRepositoryRestDocsTest extends AbstractJpaRepositoryRestDocsTest +{ + @Autowired + private SystemRepository systemRepository; + + @Test + public void createSystem() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( System.class, constraintDescriptionResolver ); + final String location = docMockMvc.perform( post( "/api/systems" ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) + .contentType( MediaType.APPLICATION_JSON ).content( IOUtils.resourceToByteArray( "/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystem.json" ) ) ) + .andExpect( status().isCreated() ) + .andExpect( header().exists( "Location" ) ) + .andDo( documentationHandler.document( requestFields( + attributes( key( "title" ).value( "Fields for system creation" ) ), + fields.withPath( "name" ).description( "The unique name of the system." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the system." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the system is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "systemUri" ).description( "The system URI of the system." ).type( JsonFieldType.STRING ), + fields.withPath( "enabled" ).description( "Specifies if this system and its code are enabled." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "descriptionProtected" ).description( "Specifies if the description contains license information that must not be changed." ).type( JsonFieldType.BOOLEAN ).optional() + ) ) ).andReturn().getResponse().getHeader( "Location" ); + + mockMvc + .perform( get( Objects.requireNonNull( location ) ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "lastUpdatedBy", is( "2h2maqu827d" ) ) ) + .andExpect( jsonPath( "name", is( "Sierra Leone Patient" ) ) ) + .andExpect( jsonPath( "code", is( "SYSTEM_SL_PATIENT" ) ) ) + .andExpect( jsonPath( "description", is( "All Sierra Leone patients." ) ) ) + .andExpect( jsonPath( "systemUri", is( "http://example.sl/patients" ) ) ) + .andExpect( jsonPath( "enabled", is( true ) ) ) + .andExpect( jsonPath( "descriptionProtected", is( false ) ) ) + .andExpect( jsonPath( "_links.self.href", is( location ) ) ); + } + + @Test + public void readSystem() throws Exception + { + final ConstrainedFields fields = new ConstrainedFields( System.class, constraintDescriptionResolver ); + final String systemId = loadSystem( "SYSTEM_SL_ORGANIZATION" ).getId().toString(); + docMockMvc.perform( get( "/api/systems/{systemId}", systemId ).header( AUTHORIZATION_HEADER_NAME, CODE_MAPPING_AUTHORIZATION_HEADER_VALUE ) ) + .andExpect( status().isOk() ) + .andDo( documentationHandler.document( links( + linkWithRel( "self" ).description( "Link to this resource itself." ), + linkWithRel( "system" ).description( "Link to this resource itself." ) ), responseFields( + attributes( key( "title" ).value( "Fields for system reading" ) ), + fields.withPath( "createdAt" ).description( "The timestamp when the resource has been created." ).type( JsonFieldType.STRING ), + fields.withPath( "lastUpdatedBy" ).description( "The ID of the user that has updated the user the last time or null if the data has been imported to the database directly." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "lastUpdatedAt" ).description( "The timestamp when the resource has been updated the last time." ).type( JsonFieldType.STRING ), + fields.withPath( "name" ).description( "The unique name of the system." ).type( JsonFieldType.STRING ), + fields.withPath( "code" ).description( "The unique code of the system." ).type( JsonFieldType.STRING ), + fields.withPath( "description" ).description( "The detailed description that describes for which purpose the system is used." ).type( JsonFieldType.STRING ).optional(), + fields.withPath( "systemUri" ).description( "The system URI of the system." ).type( JsonFieldType.STRING ), + fields.withPath( "enabled" ).description( "Specifies if this system and its code are enabled." ).type( JsonFieldType.BOOLEAN ), + fields.withPath( "descriptionProtected" ).description( "Specifies if the description contains license information that must not be changed." ).type( JsonFieldType.BOOLEAN ).optional(), + subsectionWithPath( "_links" ).description( "Links to other resources" ) + ) ) ); + } + + @Nonnull + protected System loadSystem( @Nonnull String code ) + { + final System example = new System(); + example.setCode( code ); + return systemRepository.findOne( Example.of( example, ExampleMatcher.matching().withIgnorePaths( "enabled" ) ) ).orElseThrow( () -> new AssertionError( "System does not exist: " + code ) ); + } +} \ No newline at end of file diff --git a/fhir/src/test/resources/data.sql b/fhir/src/test/resources/data.sql index adaecbc3..b69092a7 100644 --- a/fhir/src/test/resources/data.sql +++ b/fhir/src/test/resources/data.sql @@ -27,4 +27,157 @@ -- INSERT INTO fhir_code_category(id, version, created_at, last_updated_at, last_updated_by, name, code, description) -VALUES (RANDOM_UUID(), 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'Organization Unit', 'ORGANIZATION_UNIT', 'Includes the mapping for organization units.'); +VALUES ('8673a315dd274e4cbb8b1212808d4ca1', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'Organization Unit', 'ORGANIZATION_UNIT', 'Includes the mapping for organization units.'); + +INSERT INTO fhir_code(id, version, created_at, last_updated_at, last_updated_by, name, code, mapped_code, description, code_category_id) +VALUES ('348d9391c77048538cdff3c5c6830485', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'Central Hospital Freetown', 'OU_FT_CH', 'OU_6125', 'Organization unit Central Hospital in Freetown.', '8673a315dd274e4cbb8b1212808d4ca1'); + +INSERT INTO fhir_constant (id, version, created_at, last_updated_at, last_updated_by, category, name, code, data_type, value) +VALUES ('fa4a3a0eca4640e4b8323aec96bed55e', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'GENDER', 'Gender Male', 'GENDER_MALE', 'STRING', 'Male'); + +INSERT INTO fhir_script (id, version, created_at, last_updated_at, last_updated_by, code, name, description, script_type, return_type, input_type, output_type) +VALUES ('f18acd12bc854f79935d353904eadc0b', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'TRANSFORM_FHIR_IMMUNIZATION_OS', 'Transforms FHIR Immunization to option set data element', 'Transforms FHIR Immunization to an option set data element.', +'TRANSFORM_TO_DHIS', 'BOOLEAN', 'FHIR_IMMUNIZATION', 'DHIS_EVENT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('f18acd12bc854f79935d353904eadc0b', 'CONTEXT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('f18acd12bc854f79935d353904eadc0b', 'INPUT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('f18acd12bc854f79935d353904eadc0b', 'OUTPUT'); +INSERT INTO fhir_script_argument(id, version, created_at, last_updated_at, last_updated_by, script_id, name, data_type, mandatory, array_value, default_value, description) +VALUES ('44134ba8d77f4c4d90c6b434ffbe7958', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'f18acd12bc854f79935d353904eadc0b', +'dataElement', 'DATA_ELEMENT_REF', TRUE, FALSE, NULL, 'Data element with given vaccine on which option set value must be set.'); +INSERT INTO fhir_script_argument(id, version, created_at, last_updated_at, last_updated_by, script_id, name, data_type, mandatory, array_value, default_value, description) +VALUES ('404ae6f6618749148f4b80a72764c1d8', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'f18acd12bc854f79935d353904eadc0b', +'optionValuePattern', 'PATTERN', FALSE, FALSE, NULL, 'Regular expression pattern to extract subsequent integer option value from option code. If the pattern is not specified the whole code will be used as an integer value.'); +INSERT INTO fhir_script_source (id, version, created_at, last_updated_at, last_updated_by, script_id, source_text, source_type) +VALUES ('081c4642bb8344abb90faa206ad347aa', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'f18acd12bc854f79935d353904eadc0b', +'output.setIntegerOptionValue(args[''dataElement''], immunizationUtils.getMaxDoseSequence(input), 1, false, args[''optionValuePattern''], (input.hasPrimarySource()?!input.getPrimarySource():null))', 'JAVASCRIPT'); +INSERT INTO fhir_script_source_version (script_source_id, fhir_version) +VALUES ('081c4642bb8344abb90faa206ad347aa', 'DSTU3'); + +INSERT INTO fhir_executable_script (id, version, created_at, last_updated_at, last_updated_by, script_id, name, code, description) +VALUES ('1a2950cf08424dd39453284fb08789d3', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'f18acd12bc854f79935d353904eadc0b', 'CP: OPV Dose', 'CP_OPV_DOSE', 'Transforms FHIR Immunization for OPV vaccines.'); +INSERT INTO fhir_executable_script_argument(id, executable_script_id, script_argument_id, override_value, enabled) +VALUES ('4a8ba21510e946f2921fda3973836119', '1a2950cf08424dd39453284fb08789d3', '44134ba8d77f4c4d90c6b434ffbe7958', 'CODE:DE_2006104', TRUE); + +INSERT INTO fhir_script (id, version, created_at, last_updated_at, last_updated_by, code, name, description, script_type, return_type, input_type, output_type) +VALUES ('f1da6937e2fe47a4b0f38bbff7818ee1', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'TRANSFORM_FHIR_OB_BODY_WEIGHT', 'Transforms FHIR Observation Body Weight', 'Transforms FHIR Observation Body Weight to a data element and performs weight unit conversion.', +'TRANSFORM_TO_DHIS', 'BOOLEAN', 'FHIR_OBSERVATION', 'DHIS_EVENT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('f1da6937e2fe47a4b0f38bbff7818ee1', 'CONTEXT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('f1da6937e2fe47a4b0f38bbff7818ee1', 'INPUT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('f1da6937e2fe47a4b0f38bbff7818ee1', 'OUTPUT'); +INSERT INTO fhir_script_argument(id, version, created_at, last_updated_at, last_updated_by, script_id, name, data_type, mandatory, array_value, default_value, description) +VALUES ('0767919959ae45309411ac5814102372', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'f1da6937e2fe47a4b0f38bbff7818ee1', +'dataElement', 'DATA_ELEMENT_REF', TRUE, FALSE, NULL, 'Data element on which the body weight must be set.'); +INSERT INTO fhir_script_argument(id, version, created_at, last_updated_at, last_updated_by, script_id, name, data_type, mandatory, array_value, default_value, description) +VALUES ('1ef4f760de9a4c29a321a8eee5c52313', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'f1da6937e2fe47a4b0f38bbff7818ee1', +'override', 'BOOLEAN', TRUE, FALSE, 'true', 'Specifies if an existing value should be overridden.'); +INSERT INTO fhir_script_argument(id, version, created_at, last_updated_at, last_updated_by, script_id, name, data_type, mandatory, array_value, default_value, description) +VALUES ('d8cd0e7d778045d18094b448b480e6b8', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'f1da6937e2fe47a4b0f38bbff7818ee1', +'round', 'BOOLEAN', TRUE, FALSE, 'true', 'Specifies if the resulting value should be rounded.'); + +INSERT INTO fhir_system (id, version, created_at, last_updated_at, last_updated_by, name, code, system_uri, description_protected, enabled) +VALUES ('2dd51309331940d29a1fbe2a102df4a7', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'Sierra Leone Location', 'SYSTEM_SL_LOCATION', 'http://example.sl/locations', FALSE, TRUE); +INSERT INTO fhir_system (id, version, created_at, last_updated_at, last_updated_by, name, code, system_uri, description_protected, enabled) +VALUES ('c4e9ac6acc8f4c73aab60fa6775c0ca3', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'Sierra Leone Organization', 'SYSTEM_SL_ORGANIZATION', 'http://example.sl/organizations', FALSE, TRUE); + +INSERT INTO fhir_system_code(id, version, created_at, last_updated_at, last_updated_by, code_id, system_id, system_code, system_code_value) +VALUES ('c513935c9cd24357a67960f0c79bfacb', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '348d9391c77048538cdff3c5c6830485', 'c4e9ac6acc8f4c73aab60fa6775c0ca3', '982737', 'http://example.sl/organizations|982737'); + +INSERT INTO fhir_remote_subscription(id, version, created_at, last_updated_at, last_updated_by, name, code, description, fhir_version, web_hook_authorization_header, +dhis_authentication_method, dhis_username, dhis_password, remote_base_url, tolerance_millis, logging, verbose_logging, adapter_base_url, subscription_type, enabled, locked) +VALUES ('73cd99c50ca842ada53b1891fccce08f', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'HAPI FHIR JPA Server', 'DEFAULT_SUBSCRIPTION', 'HAPI FHIR JPA Server.', 'DSTU3', +'Bearer jhsj832jDShf8ehShdu7ejhDhsilwmdsgs', 'BASIC', 'admin', 'district', 'http://localhost:8082/hapifhirjpaserverexample/baseDstu3', 60000, FALSE, FALSE, +'http://localhost:8081', 'REST_HOOK_WITH_JSON_PAYLOAD', TRUE, FALSE); +INSERT INTO fhir_remote_subscription_header (remote_subscription_id, name, value, secure) +VALUES ('73cd99c50ca842ada53b1891fccce08f', 'Authorization', 'Bearer jshru38jsHdsdfy38sh38H3d', TRUE); + +INSERT INTO fhir_remote_subscription_resource (id, version, created_at, last_updated_at, last_updated_by, remote_subscription_id, fhir_resource_type, fhir_criteria_parameters, description) +VALUES ('667bfa41867c479686b6eb9f9ed4dc94', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '73cd99c50ca842ada53b1891fccce08f', 'PATIENT', '_format=json', 'Subscription for all Patients.'); +INSERT INTO fhir_remote_subscription_system (id, version, created_at, last_updated_at, last_updated_by, remote_subscription_id, fhir_resource_type, system_id) +VALUES ('ea9804a39e824d0d9cd2e417b32b1c0c', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '73cd99c50ca842ada53b1891fccce08f', 'ORGANIZATION', 'c4e9ac6acc8f4c73aab60fa6775c0ca3'); + +INSERT INTO fhir_code_category (id, version, created_at, last_updated_at, last_updated_by, name, code, description) +VALUES ('7090561ef45b411e99c065fa1d145018', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'Vaccine', 'VACCINE', 'Available vaccines.'); +INSERT INTO fhir_code(id, version, created_at, last_updated_at, last_updated_by, code_category_id, name, code, description) +VALUES ('f9462e8c653b4c6aa5028470a1ab2187', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '7090561ef45b411e99c065fa1d145018', 'DTaP', 'VACCINE_20', +'diphtheria, tetanus toxoids and acellular pertussis vaccine'); +INSERT INTO fhir_code(id, version, created_at, last_updated_at, last_updated_by, code_category_id, name, code, description) +VALUES ('02422dddb6064bb68d0fcca090182b5d', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '7090561ef45b411e99c065fa1d145018', +'DTaP, 5 pertussis antigens', 'VACCINE_106', 'diphtheria, tetanus toxoids and acellular pertussis vaccine, 5 pertussis antigens'); +INSERT INTO fhir_code(id, version, created_at, last_updated_at, last_updated_by, code_category_id, name, code, description) +VALUES ('71f5536a258745b988ac9aba362a424a', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '7090561ef45b411e99c065fa1d145018', 'MMR', 'VACCINE_03', +'measles, mumps and rubella virus vaccine'); +INSERT INTO fhir_code(id, version, created_at, last_updated_at, last_updated_by, code_category_id, name, code, description) +VALUES ('eac12b34ddeb47afa1de59ee2dac488f', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '7090561ef45b411e99c065fa1d145018', 'M/R', 'VACCINE_04', +'measles and rubella virus vaccine'); +INSERT INTO fhir_code_set(id, version, created_at, last_updated_at, last_updated_by, code_category_id, name, code, description) +VALUES ('bb66ee918e86422cbb005a90ac95a558', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '7090561ef45b411e99c065fa1d145018', 'All DTP/DTaP', 'ALL_DTP_DTAP', 'All DTP/DTaP vaccines.'); +INSERT INTO fhir_code_set_value(id, code_set_id, code_id, enabled) + SELECT RANDOM_UUID(), 'bb66ee918e86422cbb005a90ac95a558', id, TRUE FROM fhir_code WHERE code IN ('VACCINE_106', 'VACCINE_20'); + +INSERT INTO fhir_script (id, version, created_at, last_updated_at, last_updated_by, name, code, description, script_type, return_type, input_type, output_type) +VALUES ('a250e109a13542b28bdb1c050c1d384c', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '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); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('a250e109a13542b28bdb1c050c1d384c', 'CONTEXT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('a250e109a13542b28bdb1c050c1d384c', 'INPUT'); +INSERT INTO fhir_script_source (id, version, created_at, last_updated_at, last_updated_by, script_id, source_text, source_type) +VALUES ('7b94febabcf64635929a01311b25d975', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'a250e109a13542b28bdb1c050c1d384c', +'context.createReference(identifierUtils.getReferenceIdentifier(input.managingOrganization, ''ORGANIZATION''), ''CODE'')', 'JAVASCRIPT'); +INSERT INTO fhir_script_source_version (script_source_id, fhir_version) +VALUES ('7b94febabcf64635929a01311b25d975', 'DSTU3'); +INSERT INTO fhir_executable_script (id, version, created_at, last_updated_at, last_updated_by, script_id, name, code, description) +VALUES ('25a97bb47b394ed48677db4bcaa28ccf', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'a250e109a13542b28bdb1c050c1d384c', '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.'); + +INSERT INTO fhir_script (id, version, created_at, last_updated_at, last_updated_by, name, code, description, script_type, return_type, input_type, output_type) +VALUES ('2263b2969d964698bc1d17930005eef3', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'GEO Location from Patient', 'EXTRACT_FHIR_PATIENT_GEO_LOCATION', +'Extracts the GEO location form FHIR Patient.', +'EVALUATE', 'LOCATION', 'FHIR_PATIENT', NULL); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('2263b2969d964698bc1d17930005eef3', 'CONTEXT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('2263b2969d964698bc1d17930005eef3', 'INPUT'); +INSERT INTO fhir_script_source (id, version, created_at, last_updated_at, last_updated_by, script_id, source_text, source_type) +VALUES ('039ac2e650f24e4a9e4adc0515560273', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '2263b2969d964698bc1d17930005eef3', +'geoUtils.getLocation(addressUtils.getPrimaryAddress(input.address))', 'JAVASCRIPT'); +INSERT INTO fhir_script_source_version (script_source_id, fhir_version) +VALUES ('039ac2e650f24e4a9e4adc0515560273', 'DSTU3'); +INSERT INTO fhir_executable_script (id, version, created_at, last_updated_at, last_updated_by, script_id, name, code, description) +VALUES ('ef90531f443848bd83b36370dd65875a', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', '2263b2969d964698bc1d17930005eef3', 'GEO Location from Patient', 'EXTRACT_FHIR_PATIENT_GEO_LOCATION', +'Extracts the GEO location form FHIR Patient.'); + +INSERT INTO fhir_script (id, version, created_at, last_updated_at, last_updated_by, name, code, description, script_type, return_type, input_type, output_type) +VALUES ('ea8879435e944e319441c7661fe1063e', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'Transforms FHIR Patient to DHIS Person', +'TRANSFORM_FHIR_PATIENT_DHIS_PERSON', 'Transforms FHIR Patient to DHIS Person.', 'TRANSFORM_TO_DHIS', 'BOOLEAN', 'FHIR_PATIENT', 'DHIS_TRACKED_ENTITY_INSTANCE'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('ea8879435e944e319441c7661fe1063e', 'CONTEXT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('ea8879435e944e319441c7661fe1063e', 'INPUT'); +INSERT INTO fhir_script_variable (script_id, variable) VALUES ('ea8879435e944e319441c7661fe1063e', 'OUTPUT'); +INSERT INTO fhir_script_argument(id, version, created_at, last_updated_at, last_updated_by, script_id, name, data_type, mandatory, default_value, description, array_value) +VALUES ('0a7c26cb7bd343949d47a610ac231f8a', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'ea8879435e944e319441c7661fe1063e', +'lastNameAttribute', 'TRACKED_ENTITY_ATTRIBUTE_REF', TRUE, 'NAME:Last name', +'The reference of the tracked entity attribute that contains the last name of the Person.', FALSE); +INSERT INTO fhir_script_argument(id, version, created_at, last_updated_at, last_updated_by, script_id, name, data_type, mandatory, default_value, description, array_value) +VALUES ('b41dd571a1294fa6a80735ea5663e8e3', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'ea8879435e944e319441c7661fe1063e', +'firstNameAttribute', 'TRACKED_ENTITY_ATTRIBUTE_REF', TRUE, 'CODE:MMD_PER_NAM', +'The reference of the tracked entity attribute that contains the first name of the Person.', FALSE); +INSERT INTO fhir_script_argument(id, version, created_at, last_updated_at, last_updated_by, script_id, name, data_type, mandatory, default_value, description, array_value) +VALUES ('90b3c11038e44291934ce2569e8af1ba', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'ea8879435e944e319441c7661fe1063e', +'birthDateAttribute', 'TRACKED_ENTITY_ATTRIBUTE_REF', FALSE, NULL, +'The reference of the tracked entity attribute that contains the birth date of the Person.', FALSE); +INSERT INTO fhir_script_source (id, version, created_at, last_updated_at, last_updated_by, script_id, source_text, source_type) +VALUES ('b2cfaf306ede41f2bd6c448e76c429a1', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'ea8879435e944e319441c7661fe1063e', +'output.setValue(args[''lastNameAttribute''], humanNameUtils.getPrimaryName(input.name).family, context.getFhirRequest().getLastUpdated()); +output.setValue(args[''firstNameAttribute''], humanNameUtils.getSingleGiven(humanNameUtils.getPrimaryName(input.name)), context.getFhirRequest().getLastUpdated()); +output.setOptionalValue(args[''birthDateAttribute''], dateTimeUtils.getPreciseDate(input.birthDateElement), context.getFhirRequest().getLastUpdated()); +true', 'JAVASCRIPT'); +INSERT INTO fhir_script_source_version (script_source_id, fhir_version) +VALUES ('b2cfaf306ede41f2bd6c448e76c429a1', 'DSTU3'); +INSERT INTO fhir_executable_script (id, version, created_at, last_updated_at, last_updated_by, script_id, name, code, description) +VALUES ('72451c8f7492470790b8a3e0796de19e', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'ea8879435e944e319441c7661fe1063e', +'Transforms FHIR Patient to DHIS Person', 'TRANSFORM_FHIR_PATIENT_DHIS_PERSON', 'Transforms FHIR Patient to DHIS Person.'); +INSERT INTO fhir_executable_script_argument(id, executable_script_id, script_argument_id, override_value, enabled) +VALUES ('9b832b2c0a574441841147b5dc65ec91', '72451c8f7492470790b8a3e0796de19e', '90b3c11038e44291934ce2569e8af1ba', 'CODE:MMD_PER_DOB', TRUE); + +INSERT INTO fhir_rule (id, version, created_at, last_updated_at, last_updated_by, name, description, enabled, evaluation_order, fhir_resource_type, dhis_resource_type, applicable_in_script_id, transform_in_script_id) +VALUES ('5f9ebdc9852e4c8387ca795946aabc35', 0, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), '2h2maqu827d', 'FHIR Patient to Person', NULL, TRUE, 1, 'PATIENT', 'TRACKED_ENTITY', NULL, '72451c8f7492470790b8a3e0796de19e'); +INSERT INTO fhir_tracked_entity_rule (id, tracked_entity_ref, org_lookup_script_id, loc_lookup_script_id, tracked_entity_identifier_ref) +VALUES ('5f9ebdc9852e4c8387ca795946aabc35', 'NAME:Person', '25a97bb47b394ed48677db4bcaa28ccf', 'ef90531f443848bd83b36370dd65875a', 'CODE:National identifier'); + diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createCode.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createCode.json new file mode 100644 index 00000000..41b63e51 --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createCode.json @@ -0,0 +1,7 @@ +{ + "name": "Test Code", + "code": "TEST_CODE", + "mappedCode": "MAPPED_TEST_CODE", + "description": "This is a test code.", + "codeCategory": "$codeCategory" +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createCodeSet.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createCodeSet.json new file mode 100644 index 00000000..54d3d5f9 --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createCodeSet.json @@ -0,0 +1,15 @@ +{ + "name": "Vaccine Measles", + "code": "ALL_MEASLES", + "codeCategory": "$codeCategoryId", + "description": "All measles vaccine codes.", + "codeSetValues": [ + { + "code": "$codeId1" + }, + { + "code": "$codeId2", + "enabled": false + } + ] +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createConstant.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createConstant.json new file mode 100644 index 00000000..b5b1e18c --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createConstant.json @@ -0,0 +1,8 @@ +{ + "category": "GENDER", + "name": "Gender Female", + "description": "Constant for Gender option value as it is used by DHIS2.", + "code": "GENDER_FEMALE", + "dataType": "STRING", + "value": "Female" +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createExecutableScript.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createExecutableScript.json new file mode 100644 index 00000000..047699dd --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createExecutableScript.json @@ -0,0 +1,17 @@ +{ + "script": "$scriptId", + "name": "CP: Birth Weight", + "code": "CP_BIRTH_WEIGHT", + "overrideArguments": [ + { + "argument": "$dataElementArgId", + "overrideValue": "CODE:DE_2005736", + "enabled": true + }, + { + "argument": "$roundArgId", + "overrideValue": "false", + "enabled": true + } + ] +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscription.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscription.json new file mode 100644 index 00000000..ec30c698 --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscription.json @@ -0,0 +1,24 @@ +{ + "name": "Main Subscription", + "code": "MAIN_SUBSCRIPTION", + "description": "Main FHIR service on which the adapter has subscriptions.", + "enabled": true, + "locked": false, + "fhirVersion": "DSTU3", + "toleranceMillis": 2000, + "autoCreatedSubscriptionResources": [ "PATIENT" ], + "adapterEndpoint": { + "baseUrl": "http://localhist:8081", + "subscriptionType": "REST_HOOK", + "authorizationHeader": "Bearer 98a7558102b7bdc4da5c8f74ca63958c498b4bd9231bd3b0cc" + }, + "dhisEndpoint": { + "authenticationMethod": "BASIC", + "username": "admin", + "password": "district" + }, + "fhirEndpoint": { + "baseUrl": "http://localhost:8082/hapi-fhir-jpaserver-example/baseDstu3", + "headers": [ { "name": "Authorization", "value": "Bearer 1196cc744b416a2b91c8239c1a1e251c139d2a89df86427241", "secure": true } ] + } +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionResource.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionResource.json new file mode 100644 index 00000000..6946536f --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionResource.json @@ -0,0 +1,6 @@ +{ + "remoteSubscription": "$remoteSubscriptionId", + "fhirResourceType": "IMMUNIZATION", + "description": "Subscription for all immunizations.", + "fhirCriteriaParameters": null +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionSystem.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionSystem.json new file mode 100644 index 00000000..538de138 --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createRemoteSubscriptionSystem.json @@ -0,0 +1,6 @@ +{ + "remoteSubscription": "$remoteSubscriptionId", + "fhirResourceType": "LOCATION", + "system": "$systemId", + "codePrefix": "LOC_" +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScript.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScript.json new file mode 100644 index 00000000..2fe5eb91 --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScript.json @@ -0,0 +1,10 @@ +{ + "name": "Transforms FHIR Immunization to Y/N data element", + "code": "TRANSFORM_FHIR_IMMUNIZATION_YN", + "description": "Transforms FHIR Immunization to Y/N data element.", + "scriptType": "TRANSFORM_TO_DHIS", + "returnType": "BOOLEAN", + "inputType": "FHIR_IMMUNIZATION", + "outputType": "DHIS_EVENT", + "variables": [ "CONTEXT", "INPUT", "OUTPUT" ] +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptArg.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptArg.json new file mode 100644 index 00000000..b6338da8 --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptArg.json @@ -0,0 +1,9 @@ +{ + "script": "$scriptId", + "name": "weightUnit", + "description": "The resulting weight unit in which the value will be set on the data element.", + "dataType": "WEIGHT_UNIT", + "mandatory": true, + "array": false, + "defaultValue": "KILO_GRAM" +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptSource.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptSource.json new file mode 100644 index 00000000..2c123f6f --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createScriptSource.json @@ -0,0 +1,6 @@ +{ + "script": "$scriptId", + "sourceType": "JAVASCRIPT", + "sourceText": "output.setValue(args['dataElement'], vitalSignUtils.getWeight(input.value, args['weightUnit'], args['round']), null, args['override'], context.getFhirRequest().getLastUpdated())", + "fhirVersions": [ "DSTU3" ] +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystem.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystem.json new file mode 100644 index 00000000..07a36a14 --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystem.json @@ -0,0 +1,8 @@ +{ + "name": "Sierra Leone Patient", + "code": "SYSTEM_SL_PATIENT", + "description": "All Sierra Leone patients.", + "descriptionProtected": false, + "systemUri": "http://example.sl/patients", + "enabled": true +} diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystemCode.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystemCode.json new file mode 100644 index 00000000..8c123048 --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createSystemCode.json @@ -0,0 +1,5 @@ +{ + "system": "$systemId", + "code": "$codeId", + "systemCode": "982783729" +} \ No newline at end of file diff --git a/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createTrackedEntityRule.json b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createTrackedEntityRule.json new file mode 100644 index 00000000..0257c559 --- /dev/null +++ b/fhir/src/test/resources/org/dhis2/fhir/adapter/fhir/metadata/repository/createTrackedEntityRule.json @@ -0,0 +1,21 @@ +{ + "name": "FHIR Patient to Person (disabled)", + "description": "Transforms a FHIR Patient to a Person", + "enabled": false, + "evaluationOrder": 0, + "dhisResourceType": "TRACKED_ENTITY", + "fhirResourceType": "PATIENT", + "trackedEntityReference": { + "value": "Person", + "type": "NAME" + }, + "trackedEntityIdentifierReference": { + "value": "National identifier", + "type": "CODE" + }, + "applicableInScript": null, + "applicableCodeSet": null, + "transformInScript": "$transformInScriptId", + "orgUnitLookupScript": "$orgUnitLookupScriptId", + "locationLookupScript": "$locationLookupScriptId" +} diff --git a/fhir/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet b/fhir/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet new file mode 100644 index 00000000..572f9688 --- /dev/null +++ b/fhir/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet @@ -0,0 +1,11 @@ +.{{title}} +|=== +|Path|Type|Description + +{{#fields}} +|{{path}} +|{{type}} +|{{description}} + +{{/fields}} +|=== diff --git a/fhir/src/test/resources/test.properties b/fhir/src/test/resources/test.properties index 34c38a22..bfef2a46 100644 --- a/fhir/src/test/resources/test.properties +++ b/fhir/src/test/resources/test.properties @@ -27,10 +27,12 @@ # -spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 spring.datasource.username=sa spring.datasource.driver-class-name=org.h2.Driver +spring.jpa.open-in-view=false +spring.jpa.show-sql=true spring.jpa.properties.hibernate.jdbc.time_zone=UTC spring.flyway.enabled=false diff --git a/pom.xml b/pom.xml index bb613d4e..3c5194a2 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 1.8 1.8 - 3.5.0 + 3.6.0 2.0.1.RELEASE