All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hisp.dhis.BaseDhis2 Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
package org.hisp.dhis;

import static org.apache.hc.core5.http.HttpStatus.SC_FORBIDDEN;
import static org.apache.hc.core5.http.HttpStatus.SC_NOT_FOUND;
import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED;
import static org.hisp.dhis.util.CollectionUtils.asList;
import static org.hisp.dhis.util.CollectionUtils.set;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Set;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.hc.client5.http.HttpResponseException;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpHead;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.net.URIBuilder;
import org.hisp.dhis.model.IdentifiableObject;
import org.hisp.dhis.model.Objects;
import org.hisp.dhis.model.datavalueset.DataValueSetImportOptions;
import org.hisp.dhis.model.event.Events;
import org.hisp.dhis.model.event.EventsResult;
import org.hisp.dhis.query.Filter;
import org.hisp.dhis.query.Operator;
import org.hisp.dhis.query.Order;
import org.hisp.dhis.query.Paging;
import org.hisp.dhis.query.Query;
import org.hisp.dhis.query.RootJunction;
import org.hisp.dhis.query.analytics.AnalyticsQuery;
import org.hisp.dhis.query.analytics.Dimension;
import org.hisp.dhis.query.datavalue.DataValueSetQuery;
import org.hisp.dhis.query.event.EventsQuery;
import org.hisp.dhis.response.BaseHttpResponse;
import org.hisp.dhis.response.Dhis2ClientException;
import org.hisp.dhis.response.HttpStatus;
import org.hisp.dhis.response.Response;
import org.hisp.dhis.response.Status;
import org.hisp.dhis.response.object.ObjectResponse;
import org.hisp.dhis.response.objects.ObjectsResponse;
import org.hisp.dhis.util.HttpUtils;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * @author Lars Helge Overland
 */
@Slf4j
public class BaseDhis2
{
    protected static final String FIELDS_PARAM = "fields";

    protected static final String ID_FIELDS = "id,code,name,created,lastUpdated,attributeValues";

    protected static final String NAME_FIELDS = String.format(
        "%s,shortName,description", ID_FIELDS );

    protected static final String OPTION_SET_FIELDS = String.format(
        "%s,valueType,version", ID_FIELDS );

    protected static final String DATA_ELEMENT_FIELDS = String.format(
        "%1$s,aggregationType,valueType,domainType,url,legendSets[%1$s],optionSet[%2$s]",
        NAME_FIELDS, OPTION_SET_FIELDS );

    protected static final String DATA_ELEMENT_GROUP_SET_FIELDS = String.format(
        "%1$s,compulsory,dataDimension,dimensionType,dataElementGroups[%1$s]", NAME_FIELDS );

    protected static final String CATEGORY_OPTION_FIELDS = String.format(
        "%1$s,shortName,startDate,endDate,formName,categories[%1$s],organisationUnits[%1$s]", ID_FIELDS );

    protected static final String CATEGORY_OPTION_COMBO_FIELDS = String.format(
        "%1$s,ignoreApproval,dimensionItem,categoryOptions[%1$s]", ID_FIELDS );

    protected static final String CATEGORY_OPTION_GROUP_FIELDS = String.format(
        "%1$s,dataDimensionType,dimensionItemType,categoryOptions[%2$s],groupSets[%2$s]", NAME_FIELDS, ID_FIELDS );

    protected static final String CATEGORY_OPTION_GROUP_SET_FIELDS = String.format(
        "%1$s,dataDimension,dataDimensionType,categoryOptionGroups[%2$s]", NAME_FIELDS, ID_FIELDS );

    protected static final String CATEGORY_COMBO_FIELDS = String.format(
        "%1$s,dataDimensionType,skipTotal,categories[%1$s],categoryOptionCombos[%2$s]",
        ID_FIELDS, CATEGORY_OPTION_COMBO_FIELDS );

    protected static final String CATEGORY_FIELDS = String.format(
        "%1$s,dataDimensionType,dataDimension,categoryOptions[%2$s]", NAME_FIELDS, ID_FIELDS );

    protected static final String INDICATOR_TYPE_FIELDS = String.format(
        "%s,factor,number", NAME_FIELDS );

    protected static final String INDICATOR_FIELDS = String.format(
        "%1$s,annualized,numerator,numeratorDescription,denominator,denominatorDescription,url," +
            "indicatorType[%2$s]",
        NAME_FIELDS, INDICATOR_TYPE_FIELDS );

    protected static final String DATA_SET_FIELDS = String.format(
        "%1$s,formName,displayFormName,categoryCombo[%1$s],dataSetElements,dimensionItem,openFuturePeriods, " +
            "expiryDays,timelyDays,url,formType,periodType,version,dimensionItemType,aggregationType,favorite," +
            "compulsoryFieldsCompleteOnly,skipOffline,validCompleteOnly,dataElementDecoration," +
            "openPeriodsAfterCoEndDate,notifyCompletingUser,noValueRequiresComment,fieldCombinationRequired,mobile," +
            "dataEntryForm[%2$s]",
        NAME_FIELDS, ID_FIELDS );

    protected static final String ORG_UNIT_FIELDS = String.format(
        "%s,path,level,parent[%s],openingDate,closedDate,comment," +
            "url,contactPerson,address,email,phoneNumber",
        NAME_FIELDS, NAME_FIELDS );

    protected static final String ORG_UNIT_GROUP_SET_FIELDS = String.format(
        "%1$s,dataDimension,compulsory,organisationUnitGroups[%2$s]", NAME_FIELDS, ID_FIELDS );

    protected static final String TE_ATTRIBUTE_FIELDS = String.format(
        "%s,valueType,confidential,unique", NAME_FIELDS );

    protected static final String ME_FIELDS = String.format(
        "%1$s,username,surname,firstName,email,settings,programs,dataSets,authorities,organisationUnits[%2$s]",
        ID_FIELDS, ORG_UNIT_FIELDS );

    protected static final String RESOURCE_SYSTEM_INFO = "system/info";

    protected static final String DATE_FORMAT = "yyyy-MM-dd";

    private static final Set ERROR_STATUS_CODES = set(
        SC_UNAUTHORIZED, SC_FORBIDDEN, SC_NOT_FOUND );

    protected final Dhis2Config config;

    protected final ObjectMapper objectMapper;

    protected final CloseableHttpClient httpClient;

    public BaseDhis2( Dhis2Config config )
    {
        Validate.notNull( config, "Config must be specified" );

        this.config = config;

        this.objectMapper = new ObjectMapper();
        objectMapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES );
        objectMapper.setSerializationInclusion( Include.NON_NULL );
        objectMapper.setDateFormat( new SimpleDateFormat( DATE_FORMAT ) );

        this.httpClient = HttpClients.createDefault();
    }

    /**
     * Retrieves an object using HTTP GET.
     *
     * @param uriBuilder the URI builder.
     * @param query the {@link Query} filters to apply.
     * @param type the class type of the object.
     * @param  type.
     * @return the object.
     */
    protected  T getObject( URIBuilder uriBuilder, Query query, Class type )
    {
        URI url = getObjectQuery( uriBuilder, query );

        return getObjectFromUrl( url, type );
    }

    /**
     * Indicates whether an object exists using HTTP HEAD.
     *
     * @param uriBuilder the URI builder.
     * @return true if the object exists.
     */
    protected boolean objectExists( URIBuilder uriBuilder )
    {
        HttpHead request = withAuth( new HttpHead( HttpUtils.build( uriBuilder ) ) );

        try ( CloseableHttpResponse response = httpClient.execute( request ) )
        {
            return HttpStatus.OK.value() == response.getCode();
        }
        catch ( IOException ex )
        {
            return false;
        }
    }

    /**
     * Returns a {@link URI} based on the given query.
     *
     * @param uriBuilder the URI builder.
     * @param query the {@link Query} filters to apply.
     * @return a URI.
     */
    protected URI getObjectQuery( URIBuilder uriBuilder, Query query )
    {
        for ( Filter filter : query.getFilters() )
        {
            String filterValue = String.format( "%s:%s:%s",
                filter.getProperty(),
                filter.getOperator().value(),
                getQueryValue( filter ) );

            uriBuilder.addParameter( "filter", filterValue );
        }

        if ( !query.getFilters().isEmpty() && query.getRootJunction() == RootJunction.OR )
        {
            uriBuilder.addParameter( "rootJunction", "OR" );
        }

        Paging paging = query.getPaging();

        if ( paging.hasPaging() )
        {
            if ( paging.hasPage() )
            {
                uriBuilder.addParameter( "page", String.valueOf( paging.getPage() ) );
            }

            if ( paging.hasPageSize() )
            {
                uriBuilder.addParameter( "pageSize", String.valueOf( paging.getPageSize() ) );
            }
        }
        else
        {
            uriBuilder.addParameter( "paging", "false" );
        }

        Order order = query.getOrder();

        if ( order.hasOrder() )
        {
            String orderValue = order.getProperty() + ":" + order.getDirection().name().toLowerCase();

            uriBuilder.addParameter( "order", orderValue );
        }

        return HttpUtils.build( uriBuilder );
    }

    /**
     * Retrieves a dataValueSet object using HTTP GET.
     *
     * @param uriBuilder the URI builder.
     * @param query the {@link DataValueSetQuery} filters to apply.
     * @param type the class type of the object.
     * @param  type.
     * @return the object.
     */
    protected  T getDataValueSetResponse( URIBuilder uriBuilder, DataValueSetQuery query, Class type )
    {
        URI url = getDataValueSetQuery( uriBuilder, query );

        return getObjectFromUrl( url, type );
    }

    /**
     * Retrieves an analytics object using HTTP GET.
     *
     * @param uriBuilder the URI builder.
     * @param query the {@link AnalyticsQuery} filters to apply.
     * @param type the class type of the object.
     * @param  type.
     * @return the object.
     */
    protected  T getAnalyticsResponse( URIBuilder uriBuilder, AnalyticsQuery query, Class type )
    {
        URI url = getAnalyticsQuery( uriBuilder, query );

        return getObjectFromUrl( url, type );
    }

    /**
     * Returns a {@link URI} based on the given analytics query.
     *
     * @param uriBuilder the URI builder.
     * @param query the {@link AnalyticsQuery} filters to apply.
     * @return a URI.
     */
    protected URI getAnalyticsQuery( URIBuilder uriBuilder, AnalyticsQuery query )
    {
        for ( Dimension dimension : query.getDimensions() )
        {
            addParameter( uriBuilder, "dimension", dimension.getDimensionValue() );
        }

        for ( Dimension filter : query.getFilters() )
        {
            addParameter( uriBuilder, "filter", filter.getDimensionValue() );
        }

        addParameter( uriBuilder, "aggregationType", query.getAggregationType() );
        addParameter( uriBuilder, "startDate", query.getStartDate() );
        addParameter( uriBuilder, "endDate", query.getEndDate() );
        addParameter( uriBuilder, "skipMeta", query.getSkipMeta() );
        addParameter( uriBuilder, "skipData", query.getSkipData() );
        addParameter( uriBuilder, "skipRounding", query.getSkipRounding() );
        addParameter( uriBuilder, "ignoreLimit", query.getIgnoreLimit() );
        addParameter( uriBuilder, "inputIdScheme", query.getInputIdScheme() );
        addParameter( uriBuilder, "outputIdScheme", query.getOutputIdScheme() );

        return HttpUtils.build( uriBuilder );
    }

    /**
     * Returns a {@link URI} based on the given data value set import options.
     *
     * @param uriBuilder the URI builder.
     * @param options the {@link DataValueSetImportOptions} to apply.
     * @return a URI.
     */
    protected URI getDataValueSetImportQuery( URIBuilder uriBuilder, DataValueSetImportOptions options )
    {
        addParameter( uriBuilder, "async", "true" ); // Always use async
        addParameter( uriBuilder, "dataElementIdScheme", options.getDataElementIdScheme() );
        addParameter( uriBuilder, "orgUnitIdScheme", options.getOrgUnitIdScheme() );
        addParameter( uriBuilder, "categoryOptionComboIdScheme", options.getCategoryOptionComboIdScheme() );
        addParameter( uriBuilder, "idScheme", options.getIdScheme() );
        addParameter( uriBuilder, "dryRun", options.getDryRun() );
        addParameter( uriBuilder, "preheatCache", options.getPreheatCache() );
        addParameter( uriBuilder, "skipAudit", options.getSkipAudit() );

        return HttpUtils.build( uriBuilder );
    }

    /**
     * Retrieves events using HTTP GET.
     *
     * @param uriBuilder the URI builder.
     * @param query the {@link EventsQuery}.
     * @return the {@link Events} object.
     */
    protected EventsResult getEventsResponse( URIBuilder uriBuilder, EventsQuery query )
    {
        URI url = getEventsQuery( uriBuilder, query );

        return getObjectFromUrl( url, EventsResult.class );
    }

    /**
     * Returns a {@link URI} based on the given events query.
     *
     * @param uriBuilder the URI builder.
     * @param query the {@link EventsQuery}.
     * @return a URI.
     */
    protected URI getEventsQuery( URIBuilder uriBuilder, EventsQuery query )
    {
        addParameter( uriBuilder, "program", query.getProgram() );
        addParameter( uriBuilder, "programStage", query.getProgramStage() );
        addParameter( uriBuilder, "programStatus", query.getProgramStatus() );
        addParameter( uriBuilder, "followUp", query.getFollowUp() );
        addParameter( uriBuilder, "trackedEntityInstance", query.getTrackedEntityInstance() );
        addParameter( uriBuilder, "orgUnit", query.getOrgUnit() );
        addParameter( uriBuilder, "ouMode", query.getOuMode() );
        addParameter( uriBuilder, "status", query.getStatus() );
        addParameter( uriBuilder, "occuredAfter", query.getOccurredAfter() );
        addParameter( uriBuilder, "occuredBefore", query.getOccurredBefore() );
        addParameter( uriBuilder, "scheduledAfter", query.getScheduledAfter() );
        addParameter( uriBuilder, "scheduledBefore", query.getScheduledBefore() );
        addParameter( uriBuilder, "updatedAfter", query.getUpdatedAfter() );
        addParameter( uriBuilder, "updatedBefore", query.getUpdatedBefore() );
        addParameter( uriBuilder, "dataElementIdScheme", query.getDataElementIdScheme() );
        addParameter( uriBuilder, "categoryOptionComboIdScheme", query.getCategoryOptionComboIdScheme() );
        addParameter( uriBuilder, "orgUnitIdScheme", query.getOrgUnitIdScheme() );
        addParameter( uriBuilder, "programIdScheme", query.getProgramIdScheme() );
        addParameter( uriBuilder, "programStageIdScheme", query.getProgramStageIdScheme() );
        addParameter( uriBuilder, "idScheme", query.getIdScheme() );

        return HttpUtils.build( uriBuilder );
    }

    /**
     * Returns a {@link URI} based on the given dataValueSet query.
     *
     * @param uriBuilder the URI builder.
     * @param query the {@link DataValueSetQuery}.
     * @return a URI.
     */
    public URI getDataValueSetQuery( URIBuilder uriBuilder, DataValueSetQuery query )
    {
        for ( String dataElement : query.getDataElements() )
        {
            addParameter( uriBuilder, "dataElement", dataElement );
        }

        for ( String orgUnit : query.getOrgUnits() )
        {
            addParameter( uriBuilder, "orgUnit", orgUnit );
        }

        for ( String period : query.getPeriods() )
        {
            addParameter( uriBuilder, "period", period );
        }

        for ( String dataSet : query.getDataSets() )
        {
            addParameter( uriBuilder, "dataSet", dataSet );
        }

        for ( String dataElementGroup : query.getDataElementGroups() )
        {
            addParameter( uriBuilder, "dataElementGroup", dataElementGroup );
        }

        for ( String orgUnitGroup : query.getOrgUnitGroups() )
        {
            addParameter( uriBuilder, "orgUnitGroup", orgUnitGroup );
        }

        for ( String attributeOptionCombo : query.getAttributeOptionCombos() )
        {
            addParameter( uriBuilder, "attributeOptionCombo", attributeOptionCombo );
        }

        addParameter( uriBuilder, "startDate", query.getStartDate() );
        addParameter( uriBuilder, "endDate", query.getEndDate() );
        addParameter( uriBuilder, "children", query.getChildren() );
        addParameter( uriBuilder, "includeDeleted", query.getIncludeDeleted() );
        addParameter( uriBuilder, "lastUpdated", query.getLastUpdated() );
        addParameter( uriBuilder, "lastUpdatedDuration", query.getLastUpdatedDuration() );
        addParameter( uriBuilder, "limit", query.getLimit() );
        addParameter( uriBuilder, "dataElementIdScheme", query.getDataElementIdScheme() );
        addParameter( uriBuilder, "orgUnitIdScheme", query.getOrgUnitIdScheme() );
        addParameter( uriBuilder, "categoryOptionComboIdScheme", query.getCategoryOptionComboIdScheme() );
        addParameter( uriBuilder, "attributeOptionComboIdScheme", query.getAttributeOptionComboIdScheme() );
        addParameter( uriBuilder, "dataSetIdScheme", query.getDataSetIdScheme() );
        addParameter( uriBuilder, "categoryIdScheme", query.getCategoryIdScheme() );
        addParameter( uriBuilder, "categoryOptionIdScheme", query.getCategoryOptionIdScheme() );
        addParameter( uriBuilder, "idScheme", query.getIdScheme() );
        addParameter( uriBuilder, "inputOrgUnitIdScheme", query.getInputOrgUnitIdScheme() );
        addParameter( uriBuilder, "inputDataSetIdScheme", query.getInputDataSetIdScheme() );
        addParameter( uriBuilder, "inputDataElementGroupIdScheme", query.getInputDataElementGroupIdScheme() );
        addParameter( uriBuilder, "inputDataElementIdScheme", query.getInputDataElementIdScheme() );
        addParameter( uriBuilder, "inputIdScheme", query.getInputIdScheme() );

        return HttpUtils.build( uriBuilder );
    }

    /**
     * Adds a query parameter to the given {@link URIBuilder} if the given
     * parameter value is not null.
     *
     * @param uriBuilder the {@link URIBuilder}.
     * @param parameter the query parameter.
     * @param value the query parameter value.
     */
    private void addParameter( URIBuilder uriBuilder, String parameter, Object value )
    {
        if ( value != null )
        {
            uriBuilder.addParameter( parameter, value.toString() );
        }
    }

    /**
     * Converts the given filter to a query value.
     *
     * @param filter the {@link Filter}.
     * @return a query value.
     */
    private Object getQueryValue( Filter filter )
    {
        if ( Operator.IN == filter.getOperator() )
        {
            return "[" + filter.getValue() + "]";
        }
        else
        {
            return filter.getValue();
        }
    }

    /**
     * Retrieves an object.
     *
     * @param path the URL path.
     * @param id the object identifier.
     * @param type the class type of the object.
     * @param  type.
     * @return the object.
     * @throws Dhis2ClientException if access was denied or resource was not
     *         found.
     */
    protected  T getObject( String path, String id, Class type )
    {
        try
        {
            URI url = config.getResolvedUriBuilder()
                .appendPath( path )
                .appendPath( id )
                .build();

            return getObjectFromUrl( url, type );
        }
        catch ( URISyntaxException ex )
        {
            throw new RuntimeException( ex );
        }
    }

    /**
     * Executes the given {@link HttpUriRequestBase} request, which may be a
     * POST or PUT request.
     *
     * @param request the request.
     * @param object the object to pass as JSON in the request body.
     * @param type the class type for the response entity.
     * @param  class.
     * @return a {@link Response}.
     * @throws Dhis2ClientException if access denied or resource not found.
     */
    protected  T executeJsonPostPutRequest(
        HttpUriRequestBase request, Object object, Class type )
    {
        validateRequestObject( object );

        String requestBody = toJsonString( object );

        log.debug( "Request body: '{}'", requestBody );

        HttpEntity entity = new StringEntity( requestBody, StandardCharsets.UTF_8 );

        request.setHeader( HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType() );
        request.setEntity( entity );

        return executeRequest( request, type );
    }

    /**
     * Executes the given request, parses the response body and returns a
     * response message.
     *
     * @param request the request.
     * @param type the class type for the response entity.
     * @return a response message.
     * @throws Dhis2ClientException if access denied or resource not found.
     */
    private  T executeRequest( HttpUriRequestBase request, Class type )
    {
        withAuth( request );

        try ( CloseableHttpResponse response = httpClient.execute( request ) )
        {
            handleErrors( response, request.getRequestUri() );

            String responseBody = EntityUtils.toString( response.getEntity() );

            log.debug( "Response body: '{}'", responseBody );

            T responseMessage = objectMapper.readValue( responseBody, type );

            responseMessage.setHeaders( asList( response.getHeaders() ) );
            responseMessage.setHttpStatusCode( response.getCode() );

            return responseMessage;
        }
        catch ( IOException ex )
        {
            throw newDhis2ClientException( ex );
        }
        catch ( ParseException ex )
        {
            throw new Dhis2ClientException( "HTTP response could not be parsed", ex );
        }
    }

    /**
     * Executes the given request without attempting to parse a response body
     * and returns a response message.
     *
     * @param request the request.
     * @return a response message.
     * @throws Dhis2ClientException if access denied or resource not found.
     */
    protected Response executeRequest( HttpUriRequestBase request )
    {
        withAuth( request );

        try ( CloseableHttpResponse response = httpClient.execute( request ) )
        {
            handleErrors( response, request.getRequestUri() );

            HttpStatus httpStatus = HttpStatus.valueOf( response.getCode() );
            Status status = httpStatus != null && httpStatus.is2xxSuccessful() ? Status.OK : Status.ERROR;

            Response resp = new Response();

            resp.setHeaders( asList( response.getHeaders() ) );
            resp.setStatus( status );
            resp.setHttpStatusCode( response.getCode() );

            return resp;
        }
        catch ( IOException ex )
        {
            throw newDhis2ClientException( ex );
        }
    }

    /**
     * Returns a HTTP post request with JSON content type for the given URL and
     * entity.
     *
     * @param url the {@link URI}.
     * @param entity the {@link HttpEntity}.
     * @return a {@link HttpPost} request.
     */
    protected HttpPost getPostRequest( URI url, HttpEntity entity )
    {
        HttpPost request = withAuth( new HttpPost( url ) );
        request.setHeader( HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType() );
        request.setEntity( entity );
        return request;
    }

    /**
     * Retrieves an object using HTTP GET.
     *
     * @param url the fully qualified URL.
     * @param type the class type of the object.
     * @param  type.
     * @return the object.
     * @throws Dhis2ClientException if access denied or resource not found.
     */
    protected  T getObjectFromUrl( URI url, Class type )
    {
        log.debug( "Get object from URL: '{}'", url );

        try ( CloseableHttpResponse response = getJsonHttpResponse( url ) )
        {
            handleErrors( response, url.toString() );

            String responseBody = EntityUtils.toString( response.getEntity() );

            log.debug( "Response body: '{}'", responseBody );

            return objectMapper.readValue( responseBody, type );
        }
        catch ( IOException ex )
        {
            throw new Dhis2ClientException( "Failed to fetch object", ex );
        }
        catch ( ParseException ex )
        {
            throw new Dhis2ClientException( "HTTP response could not be parsed", ex );
        }
    }

    /**
     * Gets a {@link CloseableHttpResponse} for the given URL.
     *
     * @param url the URL.
     * @return a {@link CloseableHttpResponse}.
     */
    protected CloseableHttpResponse getJsonHttpResponse( URI url )
    {
        HttpGet request = withAuth( new HttpGet( url ) );
        request.setHeader( HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType() );

        log.debug( "GET request URL: '{}'", HttpUtils.asString( url ) );

        try
        {
            return httpClient.execute( request );
        }
        catch ( IOException ex )
        {
            throw new Dhis2ClientException( "Request failed", ex );
        }
    }

    /**
     * Handles error status codes, currently 401/403 and
     * 404.
     *
     * @param response {@link HttpResponse}.
     * @param url the request URL.
     * @throws Dhis2ClientException
     */
    private void handleErrors( HttpResponse response, String url )
    {
        final int code = response.getCode();

        if ( ERROR_STATUS_CODES.contains( code ) )
        {
            String message = String.format( "%s (%d)", getErrorMessage( code ), code );

            log.debug( "Error URL: '{}'", url );

            throw new Dhis2ClientException( message, code );
        }
    }

    /**
     * Return an error message for the given status code.
     *
     * @param code the status code.
     * @return an error message.
     */
    private String getErrorMessage( int code )
    {
        if ( 401 == code )
            return "Authentication failed";
        if ( 403 == code )
            return "Access was denied";
        if ( 404 == code )
            return "Object not found";
        if ( 409 == code )
            return "Conflict";
        if ( code >= 400 && code < 500 )
            return "Client error";
        if ( code >= 500 && code < 600 )
            return "Server error";
        else
            return "Error";
    }

    /**
     * Validates a request object.
     *
     * @param object the request object to validate.
     * @throws Dhis2ClientException
     */
    private void validateRequestObject( Object object )
    {
        if ( object == null )
        {
            throw new Dhis2ClientException( "Request object is null", 400 );
        }
    }

    /**
     * Write the given {@link HttpResponse} to the given {@link File}.
     *
     * @param response the response.
     * @param file the file to write the response to.
     * @throws Dhis2ClientException if the write operation failed.
     */
    protected void writeToFile( CloseableHttpResponse response, File file )
    {
        try ( FileOutputStream fileOut = FileUtils.openOutputStream( file );
            InputStream in = response.getEntity().getContent() )
        {
            IOUtils.copy( in, fileOut );
        }
        catch ( IOException ex )
        {
            throw new Dhis2ClientException( "Failed to write to file", ex );
        }
    }

    /**
     * Adds authentication to the given request.
     *
     * @param request the {@link HttpUriRequestBase}.
     * @param  class.
     * @return the request.
     */
    protected  T withAuth( T request )
    {
        return HttpUtils.withAuth( request, config );
    }

    /**
     * Serializes the given object to a JSON string.
     *
     * @param object the object to serialize.
     * @return a JSON string representation of the object.
     * @throws Dhis2ClientException if the serialization failed.
     */
    protected String toJsonString( Object object )
    {
        try
        {
            String json = objectMapper.writeValueAsString( object );

            log.debug( "Object JSON: '{}'", json );

            return json;
        }
        catch ( IOException ex )
        {
            throw new Dhis2ClientException( "Failed to deserialize JSON", ex );
        }
    }

    /**
     * Returns a {@link Dhis2ClientException} based on the given exception.
     *
     * @param ex the exception.
     * @return a {@link Dhis2ClientException}.
     */
    protected Dhis2ClientException newDhis2ClientException( IOException ex )
    {
        int statusCode = -1;

        if ( ex instanceof HttpResponseException )
        {
            statusCode = ((HttpResponseException) ex).getStatusCode();
        }

        return new Dhis2ClientException( ex.getMessage(), ex.getCause(), statusCode );
    }

    // -------------------------------------------------------------------------
    // Protected generic object methods
    // -------------------------------------------------------------------------

    /**
     * Saves a metadata object using HTTP POST.
     *
     * @param path the URL path relative to the API end point.
     * @param object the object to save.
     * @return {@link ObjectResponse} holding information about the operation.
     * @throws Dhis2ClientException if the save operation failed due to client
     *         side error.
     */
    protected ObjectResponse saveMetadataObject( String path, IdentifiableObject object )
    {
        return saveObject( path, object, ObjectResponse.class );
    }

    /**
     * Saves an object using HTTP POST.
     *
     * @param path the URL path relative to the API end point.
     * @param object the object to save.
     * @param type the class type for the response entity.
     * @param  class.
     * @return object holding information about the operation.
     * @throws Dhis2ClientException if the save operation failed due to client
     *         side error.
     */
    protected  T saveObject( String path, Object object, Class type )
    {
        URI url = config.getResolvedUrl( path );

        return executeJsonPostPutRequest( new HttpPost( url ), object, type );
    }

    /**
     * Saves an object using HTTP POST.
     *
     * @param uriBuilder the URI builder.
     * @param object the object to save.
     * @param type the class type for the response entity.
     * @param  class.
     * @return object holding information about the operation.
     * @throws Dhis2ClientException if the save operation failed due to client
     *         side error.
     */
    protected  T saveObject( URIBuilder uriBuilder, Object object, Class type )
    {
        URI url = HttpUtils.build( uriBuilder );

        return executeJsonPostPutRequest( new HttpPost( url ), object, type );
    }

    /**
     * Saves or updates metadata objects.
     *
     * @param objects the {@link Objects}.
     * @return {@link ObjectsResponse} holding information about the operation.
     */
    protected ObjectsResponse saveMetadataObjects( Objects objects )
    {
        URI url = config.getResolvedUrl( "metadata" );

        return executeJsonPostPutRequest( new HttpPost( url ), objects, ObjectsResponse.class );
    }

    /**
     * Updates an object using HTTP PUT.
     *
     * @param path the URL path relative to the API end point.
     * @param object the object to save.
     * @return {@link ObjectResponse} holding information about the operation.
     */
    protected ObjectResponse updateMetadataObject( String path, IdentifiableObject object )
    {
        return updateObject( path, object, ObjectResponse.class );
    }

    /**
     * Updates an object using HTTP PUT.
     *
     * @param path the URL path relative to the API end point.
     * @param object the object to save.
     * @param type the class type for the response entity.
     * @param  class.
     * @return object holding information about the operation.
     */
    protected  T updateObject( String path, Object object, Class type )
    {
        URI url = config.getResolvedUrl( path );

        return executeJsonPostPutRequest( new HttpPut( url ), object, type );
    }

    /**
     * Removes an object using HTTP DELETE.
     *
     * @param path the URL path relative to the API end point.
     * @return {@link ObjectResponse} holding information about the operation.
     */
    protected ObjectResponse removeMetadataObject( String path )
    {
        return removeObject( path, ObjectResponse.class );
    }

    /**
     * Removes an object using HTTP DELETE.
     *
     * @param path the URL path relative to the API end point.
     * @param type the class type for the response entity.
     * @param  class.
     * @return object holding information about the operation.
     */
    protected  T removeObject( String path, Class type )
    {
        URI url = config.getResolvedUrl( path );

        return executeRequest( new HttpDelete( url ), type );
    }

    /**
     * Retrieves an object using HTTP GET.
     *
     * @param path the URL path relative to the API end point.
     * @param type the class type of the object.
     * @param  type.
     * @return the object.
     */
    protected  T getObject( String path, Class type )
    {
        return getObjectFromUrl( config.getResolvedUrl( path ), type );
    }

    /**
     * Adds the item to the collection of the entity with the given identifier.
     *
     * @param path the object path in plural.
     * @param id the object identifier.
     * @param collection the collection path.
     * @param item the item identifier.
     * @return a {@link Response} holding information about the operation.
     */
    protected Response addToCollection( String path, String id, String collection, String item )
    {
        URI url = HttpUtils.build( config.getResolvedUriBuilder()
            .appendPath( path )
            .appendPath( id )
            .appendPath( collection )
            .appendPath( item ) );

        Response response = executeRequest( new HttpPost( url ) );

        Status status = response != null && response.getHttpStatus() != null
            && response.getHttpStatus().is2xxSuccessful() ? Status.OK : Status.ERROR;

        return new Response( status, response.getHttpStatusCode(), response.getMessage() );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy