diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/aggregate/DataValueSet.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/aggregate/DataValueSet.java index b24e152e..5d22a0e7 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/aggregate/DataValueSet.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/aggregate/DataValueSet.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import org.dhis2.fhir.adapter.dhis.model.DataValue; import org.dhis2.fhir.adapter.dhis.model.DhisResource; import org.dhis2.fhir.adapter.dhis.model.DhisResourceId; import org.dhis2.fhir.adapter.dhis.model.DhisResourceType; @@ -39,7 +40,9 @@ import javax.annotation.Nonnull; import java.io.Serializable; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @author David Katuscak @@ -104,6 +107,7 @@ public DataValueSet( boolean newResource ) this.newResource = newResource; this.modified = newResource; this.local = newResource; + this.dataValues = new ArrayList<>(); } @Override @@ -164,16 +168,6 @@ public DhisResourceType getResourceType() return DhisResourceType.DATA_VALUE_SET; } - public List getDataValues() - { - return dataValues; - } - - public void setDataValues( List dataValues ) - { - this.dataValues = dataValues; - } - @Override public boolean isNewResource() { @@ -229,4 +223,61 @@ public void setLastUpdated( ZonedDateTime lastUpdated ) { this.lastUpdated = lastUpdated; } + + public List getDataValues() + { + return dataValues; + } + + public void setDataValues( List dataValues ) + { + this.dataValues = dataValues; + } + + public boolean containsDataValue( @Nonnull String dataElementId ) + { + return getDataValues().stream().anyMatch( dv -> Objects.equals( dataElementId, dv.getDataElementId() ) ); + } + + public boolean containsDataValue( @Nonnull String dataElementId, @Nonnull String value ) + { + return getDataValues().stream() + .anyMatch( dv -> Objects.equals( dataElementId, dv.getDataElementId() ) + && dv.getValue() != null + && Objects.equals( value, String.valueOf( dv.getValue() ) ) ); + } + + public boolean containsDataValueWithValue( @Nonnull String dataElementId ) + { + return getDataValues().stream() + .filter( dv -> ( dv.getValue() != null ) ) + .anyMatch( dv -> Objects.equals( dataElementId, dv.getDataElementId() ) ); + } + + @Nonnull + public WritableDataValue getDataValue( @Nonnull String dataElementId ) + { + if ( getDataValues() == null ) + { + setDataValues( new ArrayList<>() ); + } + + WritableDataValue dataValue = getDataValues().stream() + .filter( dv -> Objects.equals( dataElementId, dv.getDataElementId() ) ) + .findFirst() + .orElse( null ); + + if ( dataValue == null ) + { + dataValue = new WritableDataValue( dataElementId, true ); + getDataValues().add( dataValue ); + } + return dataValue; + } + + @JsonIgnore + public boolean isAnyDataValueModified() + { + return (getDataValues() != null) && getDataValues().stream().anyMatch( DataValue::isModified ); + } } diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/aggregate/DataValueSetService.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/aggregate/DataValueSetService.java index 574951f4..30c32d3b 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/aggregate/DataValueSetService.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/aggregate/DataValueSetService.java @@ -37,7 +37,4 @@ public interface DataValueSetService { @Nonnull DataValueSet createOrUpdate( @Nonnull DataValueSet enrollment ); - -// @Nonnull -// DhisResourceResult find( @Nonnull String dataSetId, @Nonnull String orgUnitId, @Nonnull String period, @Nonnull UriFilterApplier uriFilterApplier, int from, int max ); } diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/model/DataElements.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/model/DataElements.java new file mode 100644 index 00000000..0366f269 --- /dev/null +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/model/DataElements.java @@ -0,0 +1,127 @@ +package org.dhis2.fhir.adapter.dhis.model; + +/* + * Copyright (c) 2004-2019, 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 com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nonnull; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author David Katuscak + */ +public class DataElements implements Serializable +{ + private static final long serialVersionUID = -4563360527610569923L; + + @JsonProperty( "dataElements" ) + private Collection dataElements; + + @JsonIgnore + private transient volatile Map dataElementsById; + + @JsonIgnore + private transient volatile Map dataElementsByName; + + @JsonIgnore + private transient volatile Map dataElementsByCode; + + public DataElements() + { + this.dataElements = Collections.emptyList(); + } + + public DataElements( @Nonnull Collection dataElements ) + { + this.dataElements = dataElements; + } + + @Nonnull + public Optional getOptional( @Nonnull Reference reference ) + { + switch ( reference.getType() ) + { + case CODE: + return getOptionalByCode( reference.getValue() ); + case NAME: + return getOptionalByName( reference.getValue() ); + case ID: + return getOptionalById( reference.getValue() ); + default: + throw new AssertionError( "Unhandled reference type: " + reference.getType() ); + } + } + + @Nonnull + public Optional getOptionalById( @Nonnull String id ) + { + Map tempDataElementsById = dataElementsById; + if ( tempDataElementsById == null ) + { + dataElementsById = tempDataElementsById = dataElements.stream() + .map( ImmutableDataElement::new ) + .collect( Collectors.toMap( DataElement::getId, de -> de ) ); + } + return Optional.ofNullable( tempDataElementsById.get( id ) ); + } + + @Nonnull + public Optional getOptionalByCode( @Nonnull String code ) + { + Map tempDataElementsByCode = dataElementsByCode; + if ( tempDataElementsByCode == null ) + { + dataElementsByCode = tempDataElementsByCode = dataElements.stream() + .filter( de -> StringUtils.isNotBlank( de.getCode() ) ) + .map( ImmutableDataElement::new ) + .collect( Collectors.toMap( DataElement::getCode, de -> de ) ); + } + return Optional.ofNullable( tempDataElementsByCode.get( code ) ); + } + + @Nonnull + public Optional getOptionalByName( @Nonnull String name ) + { + Map tempDataElementsByName = dataElementsByName; + if ( tempDataElementsByName == null ) + { + dataElementsByName = tempDataElementsByName = dataElements.stream() + .map( ImmutableDataElement::new ) + .collect( Collectors.toMap( DataElement::getName, de -> de ) ); + } + return Optional.ofNullable( tempDataElementsByName.get( name ) ); + } +} diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/sync/impl/DhisResourceRepositoryImpl.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/sync/impl/DhisResourceRepositoryImpl.java index b77cdeaa..2111a55d 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/sync/impl/DhisResourceRepositoryImpl.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/sync/impl/DhisResourceRepositoryImpl.java @@ -28,6 +28,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.dhis2.fhir.adapter.dhis.aggregate.DataValueSet; +import org.dhis2.fhir.adapter.dhis.aggregate.DataValueSetService; import org.dhis2.fhir.adapter.dhis.model.DhisResource; import org.dhis2.fhir.adapter.dhis.model.DhisResourceId; import org.dhis2.fhir.adapter.dhis.model.Reference; @@ -54,6 +56,7 @@ * * @author volsch * @author Charles Chigoriwa (ITINORDIC) + * @author David Katuscak */ @Component public class DhisResourceRepositoryImpl implements DhisResourceRepository @@ -68,13 +71,17 @@ public class DhisResourceRepositoryImpl implements DhisResourceRepository private final EventService eventService; + private final DataValueSetService dataValueSetService; + public DhisResourceRepositoryImpl( @Nonnull OrganizationUnitService organizationUnitService, - @Nonnull TrackedEntityService trackedEntityService, @Nonnull EnrollmentService enrollmentService, @Nonnull EventService eventService ) + @Nonnull TrackedEntityService trackedEntityService, @Nonnull EnrollmentService enrollmentService, + @Nonnull EventService eventService, @Nonnull DataValueSetService dataValueSetService ) { this.organizationUnitService = organizationUnitService; this.trackedEntityService = trackedEntityService; this.enrollmentService = enrollmentService; this.eventService = eventService; + this.dataValueSetService = dataValueSetService; } @Nonnull @@ -91,8 +98,10 @@ public Optional findRefreshed( @Nonnull DhisResourceId d return eventService.findOneById( dhisResourceId.getId() ); case ENROLLMENT: return enrollmentService.findOneById( dhisResourceId.getId() ); + case DATA_VALUE_SET: + throw new UnsupportedOperationException( "Finding DHIS2 DataValueSet resources is not supported." ); default: - throw new AssertionError( "Unhandled DHIS resource type: " + dhisResourceId.getType() ); + throw new AssertionError( "Unhandled DHIS2 resource type: " + dhisResourceId.getType() ); } } @@ -107,9 +116,10 @@ public Optional findRefreshedDeleted( @Nonnull DhisResou case ORGANIZATION_UNIT: case TRACKED_ENTITY: case ENROLLMENT: - throw new UnsupportedOperationException( "Retrieving deleted " + dhisResourceId.getType() + " DHIS resource items is not supported." ); + case DATA_VALUE_SET: + throw new UnsupportedOperationException( "Retrieving deleted " + dhisResourceId.getType() + " DHIS2 resource items is not supported." ); default: - throw new AssertionError( "Unhandled DHIS resource type: " + dhisResourceId.getType() ); + throw new AssertionError( "Unhandled DHIS2 resource type: " + dhisResourceId.getType() ); } } @@ -136,8 +146,11 @@ public DhisResource save( @Nonnull DhisResource resource ) case PROGRAM_STAGE_EVENT: saveEvent( (Event) resource ); break; + case DATA_VALUE_SET: + saveDataValueSet( (DataValueSet) resource ); + break; default: - throw new AssertionError( "Unhandled DHIS resource type: " + resource.getResourceType() ); + throw new AssertionError( "Unhandled DHIS2 resource type: " + resource.getResourceType() ); } return resource; } @@ -153,8 +166,10 @@ public boolean delete( @Nonnull DhisResource resource ) return enrollmentService.delete( resource.getId() ); case PROGRAM_STAGE_EVENT: return eventService.delete( resource.getId() ); + case DATA_VALUE_SET: + throw new UnsupportedOperationException( "Deleting DHIS2 DataValueSet resources is nut supported." ); default: - throw new AssertionError( "Unhandled DHIS resource type: " + resource.getResourceType() ); + throw new AssertionError( "Unhandled DHIS2 resource type: " + resource.getResourceType() ); } } @@ -248,4 +263,42 @@ else if ( enrollment.isModified() ) return updated; } + + private boolean saveDataValueSet( @Nonnull DataValueSet dataValueSet ) + { + if ( validateDataValueSet( dataValueSet ) ) + { + if ( dataValueSet.isNewResource() ) + { + logger.info( "Creating new DataValueSet." ); + dataValueSetService.createOrUpdate( dataValueSet ); + logger.info( "Created new DataValueSet for dataSetId: {}, orgUnit: {}, period: {}.", + dataValueSet.getDataSetId(), dataValueSet.getOrgUnitId(), dataValueSet.getPeriod() ); + return true; + } + else if ( dataValueSet.isModified() ) + { + logger.info( "Updating existing DataValueSet." ); + dataValueSetService.createOrUpdate( dataValueSet ); + logger.info( "Created new DataValueSet for dataSetId: {}, orgUnit: {}, period: {}.", + dataValueSet.getDataSetId(), dataValueSet.getOrgUnitId(), dataValueSet.getPeriod() ); + return true; + } + } + + return false; + } + + private boolean validateDataValueSet( @Nonnull DataValueSet dataValueSet ) + { + if ( dataValueSet.getDataSetId() != null && !dataValueSet.getDataSetId().isEmpty() && + dataValueSet.getOrgUnitId() != null && !dataValueSet.getOrgUnitId().isEmpty() && + dataValueSet.getPeriod() != null && !dataValueSet.getPeriod().isEmpty() && + dataValueSet.getDataValues() != null && !dataValueSet.getDataValues().isEmpty() ) + { + return true; + } + + return false; + } } diff --git a/fhir-dstu3/src/main/java/org/dhis2/fhir/adapter/fhir/server/provider/dstu3/Dstu3MeasureReportResourceProvider.java b/fhir-dstu3/src/main/java/org/dhis2/fhir/adapter/fhir/server/provider/dstu3/Dstu3MeasureReportResourceProvider.java new file mode 100644 index 00000000..6547ae0e --- /dev/null +++ b/fhir-dstu3/src/main/java/org/dhis2/fhir/adapter/fhir/server/provider/dstu3/Dstu3MeasureReportResourceProvider.java @@ -0,0 +1,80 @@ +package org.dhis2.fhir.adapter.fhir.server.provider.dstu3; + +/* + * Copyright (c) 2004-2019, 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 ca.uhn.fhir.rest.annotation.Count; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.RawParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.DateRangeParam; +import org.dhis2.fhir.adapter.fhir.metadata.repository.FhirClientResourceRepository; +import org.dhis2.fhir.adapter.fhir.metadata.repository.FhirClientSystemRepository; +import org.dhis2.fhir.adapter.fhir.model.FhirVersion; +import org.dhis2.fhir.adapter.fhir.repository.DhisRepository; +import org.dhis2.fhir.adapter.fhir.repository.FhirRepository; +import org.dhis2.fhir.adapter.fhir.server.provider.AbstractReadOnlyResourceProvider; +import org.hl7.fhir.dstu3.model.MeasureReport; +import org.springframework.stereotype.Component; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + +/** + * DSTU3 resource provider + * + * @author David Katuscak + */ +@Component +public class Dstu3MeasureReportResourceProvider extends AbstractReadOnlyResourceProvider +{ + public Dstu3MeasureReportResourceProvider( @Nonnull FhirClientResourceRepository fhirClientResourceRepository, + @Nonnull FhirClientSystemRepository fhirClientSystemRepository, + @Nonnull FhirRepository fhirRepository, @Nonnull DhisRepository dhisRepository ) + { + super( MeasureReport.class, fhirClientResourceRepository, fhirClientSystemRepository, fhirRepository, dhisRepository ); + } + + @Nonnull + @Override + public FhirVersion getFhirVersion() + { + return FhirVersion.DSTU3; + } + + @Search( allowUnknownParams = true ) + @Nonnull + public IBundleProvider search( @Nonnull RequestDetails requestDetails, @Nullable @Count Integer count, @Nullable @OptionalParam( name = SP_LAST_UPDATED ) DateRangeParam lastUpdatedDateRange, @Nullable @RawParam Map> filter ) + { + return search( requestDetails, count, null, lastUpdatedDateRange, filter ); + } +} diff --git a/fhir-r4/src/main/java/org/dhis2/fhir/adapter/fhir/server/provider/r4/R4MeasureReportResourceProvider.java b/fhir-r4/src/main/java/org/dhis2/fhir/adapter/fhir/server/provider/r4/R4MeasureReportResourceProvider.java new file mode 100644 index 00000000..95edc6b4 --- /dev/null +++ b/fhir-r4/src/main/java/org/dhis2/fhir/adapter/fhir/server/provider/r4/R4MeasureReportResourceProvider.java @@ -0,0 +1,80 @@ +package org.dhis2.fhir.adapter.fhir.server.provider.r4; + +/* + * Copyright (c) 2004-2019, 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 ca.uhn.fhir.rest.annotation.Count; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.RawParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.DateRangeParam; +import org.dhis2.fhir.adapter.fhir.metadata.repository.FhirClientResourceRepository; +import org.dhis2.fhir.adapter.fhir.metadata.repository.FhirClientSystemRepository; +import org.dhis2.fhir.adapter.fhir.model.FhirVersion; +import org.dhis2.fhir.adapter.fhir.repository.DhisRepository; +import org.dhis2.fhir.adapter.fhir.repository.FhirRepository; +import org.dhis2.fhir.adapter.fhir.server.provider.AbstractReadOnlyResourceProvider; +import org.hl7.fhir.r4.model.MeasureReport; +import org.springframework.stereotype.Component; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + +/** + * r4 resource provider + * + * @author David Katuscak + */ +@Component +public class R4MeasureReportResourceProvider extends AbstractReadOnlyResourceProvider +{ + public R4MeasureReportResourceProvider( @Nonnull FhirClientResourceRepository fhirClientResourceRepository, + @Nonnull FhirClientSystemRepository fhirClientSystemRepository, + @Nonnull FhirRepository fhirRepository, @Nonnull DhisRepository dhisRepository ) + { + super( MeasureReport.class, fhirClientResourceRepository, fhirClientSystemRepository, fhirRepository, dhisRepository ); + } + + @Nonnull + @Override + public FhirVersion getFhirVersion() + { + return FhirVersion.R4; + } + + @Search( allowUnknownParams = true ) + @Nonnull + public IBundleProvider search( @Nonnull RequestDetails requestDetails, @Nullable @Count Integer count, @Nullable @OptionalParam( name = SP_LAST_UPDATED ) DateRangeParam lastUpdatedDateRange, @Nullable @RawParam Map> filter ) + { + return search( requestDetails, count, null, lastUpdatedDateRange, filter ); + } +} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/DataValueSetRuleEventListener.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/DataValueSetRuleEventListener.java new file mode 100644 index 00000000..3e7e600d --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/listener/DataValueSetRuleEventListener.java @@ -0,0 +1,42 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository.listener; + +/* + * Copyright (c) 2004-2019, 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.DataValueSetRule; +import org.springframework.stereotype.Component; + +/** + * Event listener that prepares {@link DataValueSetRule} class before saving. + * + * @author David Katuscak + */ +@Component +public class DataValueSetRuleEventListener extends AbstractRuleEventListener +{ +} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveDataValueSetRuleValidator.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveDataValueSetRuleValidator.java new file mode 100644 index 00000000..4b9c5dc5 --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/metadata/repository/validator/BeforeCreateSaveDataValueSetRuleValidator.java @@ -0,0 +1,61 @@ +package org.dhis2.fhir.adapter.fhir.metadata.repository.validator; + +/* + * Copyright (c) 2004-2019, 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.DataValueSetRule; +import org.dhis2.fhir.adapter.fhir.metadata.model.TransformDataType; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; + +import javax.annotation.Nonnull; +import javax.persistence.EntityManager; + +/** + * Spring Data REST validator for {@link DataValueSetRule}. + * + * @author David Katuscak + */ +@Component +public class BeforeCreateSaveDataValueSetRuleValidator extends AbstractBeforeCreateSaveRuleValidator + implements MetadataValidator +{ + public BeforeCreateSaveDataValueSetRuleValidator( @Nonnull EntityManager entityManager ) + { + super( DataValueSetRule.class, entityManager ); + } + + @Override protected void doValidate( @Nonnull DataValueSetRule rule, @Nonnull Errors errors ) + { + validate( rule, TransformDataType.DHIS_DATA_VALUE_SET, errors ); + + //TODO: Do I need to validate some script? E.g. as in Enrollment validator? + BeforeCreateSaveFhirResourceMappingValidator.checkValidOrgLookupScript( errors, "DataValueSetRule.", "orgUnitLookupScript", rule.getFhirResourceType(), rule.getOrgUnitLookupScript() ); + BeforeCreateSaveFhirResourceMappingValidator.checkValidLocationLookupScript( errors, "DataValueSetRule.", "locationLookupScript", rule.getFhirResourceType(), rule.getLocationLookupScript() ); + } +} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/scripted/ScriptedDataValueSet.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/scripted/ScriptedDataValueSet.java new file mode 100644 index 00000000..799cb863 --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/scripted/ScriptedDataValueSet.java @@ -0,0 +1,53 @@ +package org.dhis2.fhir.adapter.fhir.transform.scripted; + +/* + * Copyright (c) 2004-2019, 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.dhis.model.WritableDataValue; +import org.dhis2.fhir.adapter.scriptable.Scriptable; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * Mutable or immutable data value set resource that can be used by scripts safely. + * + * @author David Katuscak + */ +@Scriptable +public interface ScriptedDataValueSet extends ScriptedDhisResource +{ + @Nonnull + String getDataSetId(); + + @Nonnull + String getPeriod(); + + @Nonnull + List getDataValues(); +} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/scripted/WritableScriptedDataValueSet.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/scripted/WritableScriptedDataValueSet.java new file mode 100644 index 00000000..42ed9346 --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/transform/scripted/WritableScriptedDataValueSet.java @@ -0,0 +1,319 @@ +package org.dhis2.fhir.adapter.fhir.transform.scripted; + +/* + * Copyright (c) 2004-2019, 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.converter.ConversionException; +import org.dhis2.fhir.adapter.dhis.aggregate.DataValueSet; +import org.dhis2.fhir.adapter.dhis.converter.ValueConverter; +import org.dhis2.fhir.adapter.dhis.model.DataElement; +import org.dhis2.fhir.adapter.dhis.model.DataElements; +import org.dhis2.fhir.adapter.dhis.model.DataValue; +import org.dhis2.fhir.adapter.dhis.model.DhisResourceId; +import org.dhis2.fhir.adapter.dhis.model.DhisResourceType; +import org.dhis2.fhir.adapter.dhis.model.Reference; +import org.dhis2.fhir.adapter.dhis.model.WritableDataValue; +import org.dhis2.fhir.adapter.dhis.orgunit.OrganizationUnitService; +import org.dhis2.fhir.adapter.dhis.tracker.trackedentity.TrackedEntityAttribute; +import org.dhis2.fhir.adapter.dhis.tracker.trackedentity.TrackedEntityAttributeValue; +import org.dhis2.fhir.adapter.fhir.transform.TransformerException; +import org.dhis2.fhir.adapter.fhir.transform.TransformerMappingException; +import org.dhis2.fhir.adapter.fhir.transform.fhir.impl.util.ScriptedDateTimeUtils; +import org.dhis2.fhir.adapter.scriptable.ScriptMethod; +import org.dhis2.fhir.adapter.scriptable.ScriptMethodArg; +import org.dhis2.fhir.adapter.scriptable.ScriptType; +import org.dhis2.fhir.adapter.scriptable.Scriptable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.Serializable; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +/** + * Mutable data value set resource that can be used by scripts safely. + * + * @author David Katuscak + */ +@Scriptable +@ScriptType( value = "DataValueSet", var = "dataValueSet", transformDataType = "DHIS_DATA_VALUE_SET", + description = "Data Value Set. If Data Value Set is not new and will be modified, it will be persisted." ) +public class WritableScriptedDataValueSet implements ScriptedDataValueSet, Serializable +{ + private final DataElements dataElements; + + private final DataValueSet dataValueSet; + + private final OrganizationUnitService organizationUnitService; + + private final ValueConverter valueConverter; + + public WritableScriptedDataValueSet ( @Nonnull DataElements dataElements, @Nonnull DataValueSet dataValueSet, + @Nonnull OrganizationUnitService organizationUnitService, @Nonnull ValueConverter valueConverter ) + { + this.dataElements = dataElements; + this.dataValueSet = dataValueSet; + this.organizationUnitService = organizationUnitService; + this.valueConverter = valueConverter; + } + + @Nullable + @Override + @ScriptMethod( description = "Returns the ID of the DataValueSet on DHIS2. Return null if the instance is new." ) + public String getId() + { + return dataValueSet.getId(); + } + + @Nonnull + @Override + public DhisResourceType getResourceType() + { + return dataValueSet.getResourceType(); + } + + @Nullable + @Override + public DhisResourceId getResourceId() + { + return dataValueSet.getResourceId(); + } + + @Override + @ScriptMethod( description = "Returns if the data value set is new ans has not yet been saved on DHIS2." ) + public boolean isNewResource() + { + return dataValueSet.isNewResource(); + } + + @Override + public boolean isLocal() + { + return dataValueSet.isLocal(); + } + + @Override public boolean isDeleted() + { + return false; + } + + @Nullable + @Override + @ScriptMethod( description = "Returns the date and time when the resource has been updated the last time or null if this is a new resource." ) + public ZonedDateTime getLastUpdated() + { + return dataValueSet.getLastUpdated(); + } + + @Nullable + @Override + @ScriptMethod( description = "Returns the ID of the organisation unit on DHIS2 this data value set holds data for." ) + public String getOrganizationUnitId() + { + return dataValueSet.getOrgUnitId(); + } + + @ScriptMethod( description = "Sets the organization unit ID of the organization unit to which this data value set belongs to on DHIS2 (must not be null).", + args = @ScriptMethodArg( value = "orgUnitId", description = "The organization unit ID to which the data value set belongs to" ), + returnDescription = "Returns true each time." ) + public boolean setOrganizationUnitId( @Nullable String orgUnitId ) + { + if ( orgUnitId == null ) + { + throw new TransformerMappingException( "Organization unit ID of data value set must not be null." ); + } + + //I expect that all orgUnits are in place. In the future, if a dynamic OrgUnit resolution feature should be + // in place, then the logic can be changed to use that feature + Reference orgUnitReference = Reference.createIdReference( orgUnitId ); + if ( organizationUnitService.findOneByReference( orgUnitReference ).isPresent() ) + { + if ( !Objects.equals( dataValueSet.getOrgUnitId(), orgUnitId ) ) + { + dataValueSet.setModified( true ); + } + + dataValueSet.setOrgUnitId( orgUnitId ); + } + else + { + throw new TransformerMappingException( "Organization unit with provided Organization unit ID (" + orgUnitId + ") does not exist." ); + } + + return true; + } + + @Nullable + @Override + public ScriptedTrackedEntityInstance getTrackedEntityInstance() + { + return null; + } + + @Nonnull + @Override + @ScriptMethod( description = "Returns the data set ID on DHIS2 this data value set holds data for." ) + public String getDataSetId() + { + return dataValueSet.getDataSetId(); + } + + @ScriptMethod( description = "Sets the data set ID of the data set to which this data value set belongs to on DHIS2 (must not be null).", + args = @ScriptMethodArg( value = "dataSetId", description = "The data set ID to which the data value set belongs to" ), + returnDescription = "Returns true each time." ) + public boolean setDataSetId( @Nullable String dataSetId ) + { + //TODO: Consider fetching all available dataSets from https://play.dhis2.org/dev/api/dataSets and validate + + if ( dataSetId == null ) + { + throw new TransformerMappingException( "Data set ID of data value set must not be null." ); + } + if ( !Objects.equals( dataValueSet.getDataSetId(), dataSetId ) ) + { + dataValueSet.setModified( true ); + } + dataValueSet.setDataSetId( dataSetId ); + + return true; + } + + @Nonnull + @Override + @ScriptMethod( description = "Returns the period on DHIS2 this data value set holds data for." ) + public String getPeriod() + { + return dataValueSet.getPeriod(); + } + + @ScriptMethod( description = "Sets the period to which this data value set belongs to on DHIS2 (must not be null).", + args = @ScriptMethodArg( value = "period", description = "The period to which the data value set belongs to" ), + returnDescription = "Returns true each time." ) + public boolean setPeriod( @Nullable String period ) + { + + //TODO: Consider to create a PeriodUtils class and add some more comprehensive validation of period + if ( period == null ) + { + throw new TransformerMappingException( "Period of data value set must not be null." ); + } + if ( !Objects.equals( dataValueSet.getPeriod(), period ) ) + { + dataValueSet.setModified( true ); + } + dataValueSet.setPeriod( period ); + + return true; + } + + @Nonnull + @Override + public List getDataValues() + { + return dataValueSet.getDataValues(); + } + + @ScriptMethod( description = "Sets the data value of a data value set (must not be null). " + + "This will be skipped when the specified last updated date is before the current last updated data and the user which has last modified the data value set was not the adapter itself.", + returnDescription = "Returns only true if updating the data value has not been skipped since the specified last updated date is before the current last updated date.", + args = { + @ScriptMethodArg( value = "dataElementReference", description = "The reference object to the data element." ), + @ScriptMethodArg( value = "value", description = "The value that should be set for given data element. The value will be converted to the required type automatically (if possible)." ), + @ScriptMethodArg( value = "lastUpdated", description = "The last updated timestamp of the data value that should be assigned to the data value set. This value can be null if check should not be made." ) + } ) + public boolean setDataValue( @Nonnull Reference dataElementReference, @Nullable Object value, + @Nullable Object lastUpdated ) throws TransformerException + { + + final DataElement dataElement = dataElements.getOptional( dataElementReference ).orElseThrow( () -> + new TransformerScriptException( "Data element \"" + dataElementReference + "\" does not exist." ) ); + ZonedDateTime convertedLastUpdated = ScriptedDateTimeUtils.toZonedDateTime( lastUpdated, valueConverter ); + + return setDataValue( dataElement, value, convertedLastUpdated ); + } + + private boolean setDataValue( @Nonnull DataElement dataElement, @Nullable Object value, + @Nullable ZonedDateTime lastUpdated ) throws TransformerException + { + if ( value == null ) + { + throw new TransformerMappingException( "Data value of data value set must not be null." ); + } + + Object convertedValue; + try + { + convertedValue = valueConverter.convert( value, dataElement.getValueType(), String.class ); + } + catch ( ConversionException e ) + { + throw new TransformerMappingException( "Data value of data element \"" + dataElement.getName() + "\" could not be converted: " + e.getMessage(), e ); + } + + final WritableDataValue dataValue = dataValueSet.getDataValue( dataElement.getId() ); + if ( (lastUpdated != null) && (dataValue.getLastUpdated() != null) && dataValue.getLastUpdated().isAfter( lastUpdated ) ) + { + return false; + } + + if ( !Objects.equals( dataValue.getValue(), convertedValue ) ) + { + dataValueSet.setModified( true ); + } + dataValue.setValue( convertedValue ); + + return true; + } + + @Override + @ScriptMethod( description = "Validates the content of the data value set and throws an exception if the content is invalid." ) + public void validate() throws TransformerException + { + if ( dataValueSet.getOrgUnitId() == null ) + { + throw new TransformerMappingException( "Organization unit ID of data value set has not been specified." ); + } + + if ( dataValueSet.getDataSetId() == null ) + { + throw new TransformerMappingException( "Data set ID of data value set has not been specified." ); + } + + if ( dataValueSet.getPeriod() == null ) + { + throw new TransformerMappingException( "Period of data value set has not been specified." ); + } + + if ( dataValueSet.getDataValues() == null || dataValueSet.getDataValues().isEmpty() ) + { + throw new TransformerMappingException( "No data value for data value set has been specified." ); + } + } +}