<commit_before>package org.hisp.dhis.dataset.notifications;

/*
 * Copyright (c) 2004-2017, 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.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hisp.dhis.common.DeliveryChannel;
import org.hisp.dhis.commons.util.TextUtils;
import org.hisp.dhis.dataelement.DataElementCategoryService;
import org.hisp.dhis.dataset.CompleteDataSetRegistration;
import org.hisp.dhis.dataset.CompleteDataSetRegistrationService;
import org.hisp.dhis.dataset.DataSet;
import org.hisp.dhis.i18n.I18nFormat;
import org.hisp.dhis.i18n.I18nManager;
import org.hisp.dhis.message.MessageService;
import org.hisp.dhis.message.MessageType;
import org.hisp.dhis.notification.NotificationMessage;
import org.hisp.dhis.notification.NotificationMessageRenderer;
import org.hisp.dhis.notification.SendStrategy;
import org.hisp.dhis.organisationunit.OrganisationUnit;
import org.hisp.dhis.organisationunit.OrganisationUnitService;
import org.hisp.dhis.outboundmessage.BatchResponseStatus;
import org.hisp.dhis.period.Period;
import org.hisp.dhis.period.PeriodService;
import org.hisp.dhis.program.message.ProgramMessage;
import org.hisp.dhis.program.message.ProgramMessageRecipients;
import org.hisp.dhis.program.message.ProgramMessageService;
import org.hisp.dhis.program.notification.NotificationTrigger;
import org.hisp.dhis.system.util.DateUtils;
import org.hisp.dhis.user.User;
import org.hisp.dhis.user.UserGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by zubair on 04.07.17.
 */

@Transactional
public class DefaultDataSetNotificationService
    implements DataSetNotificationService
{
    private static final Log log = LogFactory.getLog( DefaultDataSetNotificationService.class );

    private static final String SUMMARY_TEXT = "Organisation units : %d" + TextUtils.LN + "Period : %s" + TextUtils.LN + "DataSet : %s";
    private static final String SUMMARY_SUBJECT = " DataSet Summary";
    private static final String PENDING = "Pending";
    private static final String OVERDUE = "Overdue";
    private static final String TEXT_SEPARATOR =  TextUtils.LN + TextUtils.LN;

    private final ImmutableMap<DeliveryChannel, BiFunction<Set<OrganisationUnit>,ProgramMessageRecipients, ProgramMessageRecipients>> RECIPIENT_MAPPER =
        new ImmutableMap.Builder<DeliveryChannel, BiFunction<Set<OrganisationUnit>,ProgramMessageRecipients,ProgramMessageRecipients>>()
            .put( DeliveryChannel.SMS, this::resolvePhoneNumbers )
            .put( DeliveryChannel.EMAIL, this::resolveEmails )
            .build();

    private final ImmutableMap<Boolean, Function<DataSetNotificationTemplate, Integer>> DAYS_RESOLVER =
        new ImmutableMap.Builder<Boolean, Function<DataSetNotificationTemplate, Integer>>()
            .put( false, DataSetNotificationTemplate::getRelativeScheduledDays )  // Overdue reminder
            .put( true, template -> template.getRelativeScheduledDays() * -1 )  // Future reminder
            .build();

    private final ImmutableMap<DeliveryChannel, Predicate<OrganisationUnit>> VALIDATOR =
        new ImmutableMap.Builder<DeliveryChannel, Predicate<OrganisationUnit>>()
            .put( DeliveryChannel.SMS, ou ->  ou.getPhoneNumber() != null && !ou.getPhoneNumber().isEmpty() ) // Valid Ou phoneNumber
            .put( DeliveryChannel.EMAIL, ou ->  ou.getEmail() != null && !ou.getEmail().isEmpty() ) // Valid Ou Email
            .build();

    private final BiFunction<SendStrategy, Set<DataSetNotificationTemplate>, Set<DataSetNotificationTemplate>> segregator = ( s, t )  -> t.parallelStream()
        .filter( f -> s.equals( f.getSendStrategy() ) )
        .collect( Collectors.toSet() );

    // -------------------------------------------------------------------------
    // Dependencies
    // -------------------------------------------------------------------------

    @Autowired
    private DataSetNotificationTemplateService dsntService;

    @Autowired
    private MessageService internalMessageService;

    @Autowired
    private ProgramMessageService externalMessageService;

    @Autowired
    private NotificationMessageRenderer<CompleteDataSetRegistration> renderer;

    @Autowired
    private CompleteDataSetRegistrationService completeDataSetRegistrationService;

    @Autowired
    private PeriodService periodService;

    @Autowired
    private DataElementCategoryService categoryService;

    @Autowired
    private I18nManager i18nManager;

    @Autowired
    private OrganisationUnitService organisationUnitService;

    // -------------------------------------------------------------------------
    // Implementation
    // -------------------------------------------------------------------------

    @Override
    public void sendScheduledDataSetNotificationsForDay( Date day )
    {
        List<MessageBatch> batches = new ArrayList<>();

        List<DataSetNotificationTemplate> scheduledTemplates =
            dsntService.getScheduledNotifications( NotificationTrigger.SCHEDULED_DAYS_DUE_DATE );

        if ( scheduledTemplates == null || scheduledTemplates.isEmpty() )
        {
            return;
        }

        Map<SendStrategy, Set<DataSetNotificationTemplate>> sendStrategySetMap = createMapBasedOnStrategy( scheduledTemplates );

        batches.add( createBatchForSingleNotifications( sendStrategySetMap.getOrDefault( SendStrategy.SINGLE_NOTIFICATION, Sets.newHashSet() ) ) );
        batches.add( createBatchForSummaryNotifications( sendStrategySetMap.getOrDefault( SendStrategy.COLLECTIVE_SUMMARY, Sets.newHashSet() ) ) );

        batches.parallelStream().forEach( this::sendAll );
    }

    @Override
    public void sendCompleteDataSetNotifications( CompleteDataSetRegistration registration )
    {
        if ( registration == null )
        {
            return;
        }

        List<DataSetNotificationTemplate> templates = dsntService.getCompleteNotifications( registration.getDataSet() );

        if ( templates == null || templates.isEmpty() )
        {
            return;
        }

        MessageBatch batch = createMessageBatch( createBatchForCompletionNotifications( registration, Sets.newHashSet( templates ) ) );

        sendAll( batch );
    }

    // -------------------------------------------------------------------------
    // Supportive methods
    // -------------------------------------------------------------------------

    private Map<SendStrategy, Set<DataSetNotificationTemplate>> createMapBasedOnStrategy( List<DataSetNotificationTemplate> templates )
    {
        Map<SendStrategy, Set<DataSetNotificationTemplate>> sendStrategySetMap = new HashMap<>();

        Stream.of( SendStrategy.values() ).forEach( ss -> sendStrategySetMap.put( ss, segregator.apply( ss, Sets.newHashSet( templates ) ) ) );

        return sendStrategySetMap;
    }

    private MessageBatch createBatchForSummaryNotifications( Set<DataSetNotificationTemplate> templates )
    {
        MessageBatch batch = new MessageBatch();

        String messageText = "";

        boolean summaryCreated = false;

        Long pendingOus;

        for ( DataSetNotificationTemplate template : templates )
        {<commit_msg>code style<commit_after>package org.hisp.dhis.dataset.notifications;

/*
 * Copyright (c) 2004-2017, 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.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hisp.dhis.common.DeliveryChannel;
import org.hisp.dhis.commons.util.TextUtils;
import org.hisp.dhis.dataelement.DataElementCategoryService;
import org.hisp.dhis.dataset.CompleteDataSetRegistration;
import org.hisp.dhis.dataset.CompleteDataSetRegistrationService;
import org.hisp.dhis.dataset.DataSet;
import org.hisp.dhis.i18n.I18nFormat;
import org.hisp.dhis.i18n.I18nManager;
import org.hisp.dhis.message.MessageService;
import org.hisp.dhis.message.MessageType;
import org.hisp.dhis.notification.NotificationMessage;
import org.hisp.dhis.notification.NotificationMessageRenderer;
import org.hisp.dhis.notification.SendStrategy;
import org.hisp.dhis.organisationunit.OrganisationUnit;
import org.hisp.dhis.organisationunit.OrganisationUnitService;
import org.hisp.dhis.outboundmessage.BatchResponseStatus;
import org.hisp.dhis.period.Period;
import org.hisp.dhis.period.PeriodService;
import org.hisp.dhis.program.message.ProgramMessage;
import org.hisp.dhis.program.message.ProgramMessageRecipients;
import org.hisp.dhis.program.message.ProgramMessageService;
import org.hisp.dhis.program.notification.NotificationTrigger;
import org.hisp.dhis.system.util.DateUtils;
import org.hisp.dhis.user.User;
import org.hisp.dhis.user.UserGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by zubair on 04.07.17.
 */

@Transactional
public class DefaultDataSetNotificationService
    implements DataSetNotificationService
{
    private static final Log log = LogFactory.getLog( DefaultDataSetNotificationService.class );

    private static final String SUMMARY_TEXT = "Organisation units : %d" + TextUtils.LN + "Period : %s" + TextUtils.LN + "DataSet : %s";
    private static final String SUMMARY_SUBJECT = " DataSet Summary";
    private static final String PENDING = "Pending";
    private static final String OVERDUE = "Overdue";
    private static final String TEXT_SEPARATOR =  TextUtils.LN + TextUtils.LN;

    private final ImmutableMap<DeliveryChannel, BiFunction<Set<OrganisationUnit>,ProgramMessageRecipients, ProgramMessageRecipients>> RECIPIENT_MAPPER =
        new ImmutableMap.Builder<DeliveryChannel, BiFunction<Set<OrganisationUnit>,ProgramMessageRecipients,ProgramMessageRecipients>>()
            .put( DeliveryChannel.SMS, this::resolvePhoneNumbers )
            .put( DeliveryChannel.EMAIL, this::resolveEmails )
            .build();

    private final ImmutableMap<Boolean, Function<DataSetNotificationTemplate, Integer>> DAYS_RESOLVER =
        new ImmutableMap.Builder<Boolean, Function<DataSetNotificationTemplate, Integer>>()
            .put( false, DataSetNotificationTemplate::getRelativeScheduledDays )  // Overdue reminder
            .put( true, template -> template.getRelativeScheduledDays() * -1 )  // Future reminder
            .build();

    private final ImmutableMap<DeliveryChannel, Predicate<OrganisationUnit>> VALIDATOR =
        new ImmutableMap.Builder<DeliveryChannel, Predicate<OrganisationUnit>>()
            .put( DeliveryChannel.SMS, ou ->  ou.getPhoneNumber() != null && !ou.getPhoneNumber().isEmpty() ) // Valid Ou phoneNumber
            .put( DeliveryChannel.EMAIL, ou ->  ou.getEmail() != null && !ou.getEmail().isEmpty() ) // Valid Ou Email
            .build();

    private final BiFunction<SendStrategy, Set<DataSetNotificationTemplate>, Set<DataSetNotificationTemplate>> SEGREGATOR = ( s, t )  -> t.parallelStream()
        .filter( f -> s.equals( f.getSendStrategy() ) )
        .collect( Collectors.toSet() );

    // -------------------------------------------------------------------------
    // Dependencies
    // -------------------------------------------------------------------------

    @Autowired
    private DataSetNotificationTemplateService dsntService;

    @Autowired
    private MessageService internalMessageService;

    @Autowired
    private ProgramMessageService externalMessageService;

    @Autowired
    private NotificationMessageRenderer<CompleteDataSetRegistration> renderer;

    @Autowired
    private CompleteDataSetRegistrationService completeDataSetRegistrationService;

    @Autowired
    private PeriodService periodService;

    @Autowired
    private DataElementCategoryService categoryService;

    @Autowired
    private I18nManager i18nManager;

    @Autowired
    private OrganisationUnitService organisationUnitService;

    // -------------------------------------------------------------------------
    // Implementation
    // -------------------------------------------------------------------------

    @Override
    public void sendScheduledDataSetNotificationsForDay( Date day )
    {
        List<MessageBatch> batches = new ArrayList<>();

        List<DataSetNotificationTemplate> scheduledTemplates =
            dsntService.getScheduledNotifications( NotificationTrigger.SCHEDULED_DAYS_DUE_DATE );

        if ( scheduledTemplates == null || scheduledTemplates.isEmpty() )
        {
            return;
        }

        Map<SendStrategy, Set<DataSetNotificationTemplate>> sendStrategySetMap = createMapBasedOnStrategy( scheduledTemplates );

        batches.add( createBatchForSingleNotifications( sendStrategySetMap.getOrDefault( SendStrategy.SINGLE_NOTIFICATION, Sets.newHashSet() ) ) );
        batches.add( createBatchForSummaryNotifications( sendStrategySetMap.getOrDefault( SendStrategy.COLLECTIVE_SUMMARY, Sets.newHashSet() ) ) );

        batches.parallelStream().forEach( this::sendAll );
    }

    @Override
    public void sendCompleteDataSetNotifications( CompleteDataSetRegistration registration )
    {
        if ( registration == null )
        {
            return;
        }

        List<DataSetNotificationTemplate> templates = dsntService.getCompleteNotifications( registration.getDataSet() );

        if ( templates == null || templates.isEmpty() )
        {
            return;
        }

        MessageBatch batch = createMessageBatch( createBatchForCompletionNotifications( registration, Sets.newHashSet( templates ) ) );

        sendAll( batch );
    }

    // -------------------------------------------------------------------------
    // Supportive methods
    // -------------------------------------------------------------------------

    private Map<SendStrategy, Set<DataSetNotificationTemplate>> createMapBasedOnStrategy( List<DataSetNotificationTemplate> templates )
    {
        Map<SendStrategy, Set<DataSetNotificationTemplate>> sendStrategySetMap = new HashMap<>();

        Stream.of( SendStrategy.values() ).forEach( ss -> sendStrategySetMap.put( ss, SEGREGATOR.apply( ss, Sets.newHashSet( templates ) ) ) );

        return sendStrategySetMap;
    }

    private MessageBatch createBatchForSummaryNotifications( Set<DataSetNotificationTemplate> templates )
    {
        MessageBatch batch = new MessageBatch();

        String messageText = "";

        boolean summaryCreated = false;

        Long pendingOus;

        for ( DataSetNotificationTemplate template : templates )
        {
{'fraction_non_alphanumeric': 0.12854859158811532, 'fraction_numerical': 0.0029766826525549858, 'mean_word_length': 3.2707156308851224, 'pattern_counts': {'":': 0, '<': 65, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '24243', 'n_tokens_mistral': 4652, 'n_tokens_neox': 4469, 'n_words': 1169}