<commit_before>package com.atlassian.jira.plugins.dvcs.spi.bitbucket.activity;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.atlassian.jira.plugins.dvcs.activity.RepositoryActivityDao;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryActivitySynchronizer;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryCommitMapping;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryPullRequestActivityMapping;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryPullRequestCommentActivityMapping;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryPullRequestMapping;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryPullRequestUpdateActivityMapping;
import com.atlassian.jira.plugins.dvcs.dao.RepositoryDao;
import com.atlassian.jira.plugins.dvcs.model.Repository;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.BitbucketClientRemoteFactory;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.activeobjects.BitbucketPullRequestCommitMapping;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.client.BitbucketRemoteClient;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.client.ClientUtils;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequest;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestActivityInfo;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestApprovalActivity;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestBaseActivity;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestCommentActivity;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestUpdateActivity;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.restpoints.PullRequestRemoteRestpoint;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.dao.BitbucketPullRequestDao;

// TODO failure recovery + rename to stateful if will be stateful
public class BitbucketRepositoryActivitySynchronizer implements RepositoryActivitySynchronizer
{

    private final BitbucketClientRemoteFactory clientFactory;
    private final RepositoryActivityDao dao;
    private final RepositoryDao repositoryDao;
    private final PullRequestContextManager pullRequestContextManager;

    public BitbucketRepositoryActivitySynchronizer(BitbucketClientRemoteFactory clientFactory, RepositoryActivityDao dao,
            RepositoryDao repositoryDao, BitbucketPullRequestDao pullRequestDao)
    {
        super();
        this.clientFactory = clientFactory;
        this.dao = dao;
        this.repositoryDao = repositoryDao;
        this.pullRequestContextManager = new PullRequestContextManager(pullRequestDao, dao);
    }

    @Override
    public void synchronize(Repository forRepository, boolean softSync)
    {
        if (!softSync)
        {
            dao.removeAll(forRepository);
            forRepository.setActivityLastSync(null);
        }

        BitbucketRemoteClient remoteClient = clientFactory.getForRepository(forRepository, 2);
        PullRequestRemoteRestpoint pullRestpoint = remoteClient.getPullRequestAndCommentsRemoteRestpoint();

        //
        // get activities iterator
        //
        Iterable<BitbucketPullRequestActivityInfo> activities = pullRestpoint.getRepositoryActivity(forRepository.getOrgName(),
                forRepository.getSlug(), forRepository.getActivityLastSync());

        pullRequestContextManager.clear(forRepository);

        Date lastActivitySyncDate = forRepository.getActivityLastSync();

        Date previousActivityDate = null;
        //
        // check whether there's some interesting issue keys in activity
        // and persist it if yes
        //
        try
        {
            for (BitbucketPullRequestActivityInfo info : activities)
            {
                Date activityDate = ClientUtils.extractActivityDate(info.getActivity());
                if (lastActivitySyncDate == null)
                {
                    lastActivitySyncDate = activityDate;
                } else
                {
                    if (activityDate != null && activityDate.after(lastActivitySyncDate))
                    {
                        lastActivitySyncDate = activityDate;
                    }
                }

                // filtering duplicated activities in response
                // TODO implement better checking whether activity is duplicated than comparing dates
                if (!activityDate.equals(previousActivityDate))
                {
                    PullRequestContext pullRequestContext = pullRequestContextManager.getPullRequestContext(forRepository.getId(), info
                            .getPullRequest().getId());

                    processActivity(info, forRepository, pullRestpoint, pullRequestContext);

                    pullRequestContextManager.save(pullRequestContext);
                }
                previousActivityDate = activityDate;
            }

            for (PullRequestContext pullRequestContext : pullRequestContextManager.getPullRequestRequestContexts(forRepository.getId()))
            {
                // when soft sync, it could happen that we have no update activities and therefore no last update activity and iterator
                if (pullRequestContext.getLastUpdateActivity() != null)
                {
                    fillActivityCommits(forRepository, null, pullRequestContext);
                }

                dao.updatePullRequestIssueKeys(forRepository, pullRequestContext.getLocalPullRequestId());
            }
        } finally
        {
            pullRequestContextManager.clear(forRepository);
        }

        // { finally
        repositoryDao.setLastActivitySyncDate(forRepository.getId(), lastActivitySyncDate);
    }

    // -------------------------------------------------------------------------------------------------------
    // -------------------------------------------------------------------------------------------------------
    // Helpers ...
    // -------------------------------------------------------------------------------------------------------
    // -------------------------------------------------------------------------------------------------------

    private void processActivity(BitbucketPullRequestActivityInfo info, Repository forRepository, PullRequestRemoteRestpoint pullRestpoint,
            PullRequestContext pullRequestContext)
    {
        int localPullRequestId = ensurePullRequestPresent(forRepository, pullRestpoint, info, pullRequestContext);
        BitbucketPullRequestBaseActivity activity = info.getActivity();

        if (isUpdateActivity(activity))
        {
            pullRequestContext.setLastUpdateActivity((BitbucketPullRequestUpdateActivity) info.getActivity());
        } else
        {
            dao.saveActivity(forRepository, toDaoModel(info.getActivity(), forRepository.getId(), localPullRequestId));
        }
    }

    private boolean isUpdateActivity(BitbucketPullRequestBaseActivity activity)
    {
        return activity instanceof BitbucketPullRequestUpdateActivity<commit_msg>BBC-388 Saving last activity sync date in finally block to prevent duplicate entries if synchronization fails<commit_after>package com.atlassian.jira.plugins.dvcs.spi.bitbucket.activity;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.atlassian.jira.plugins.dvcs.activity.RepositoryActivityDao;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryActivitySynchronizer;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryCommitMapping;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryPullRequestActivityMapping;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryPullRequestCommentActivityMapping;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryPullRequestMapping;
import com.atlassian.jira.plugins.dvcs.activity.RepositoryPullRequestUpdateActivityMapping;
import com.atlassian.jira.plugins.dvcs.dao.RepositoryDao;
import com.atlassian.jira.plugins.dvcs.model.Repository;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.BitbucketClientRemoteFactory;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.activeobjects.BitbucketPullRequestCommitMapping;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.client.BitbucketRemoteClient;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.client.ClientUtils;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequest;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestActivityInfo;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestApprovalActivity;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestBaseActivity;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestCommentActivity;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.model.BitbucketPullRequestUpdateActivity;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.clientlibrary.restpoints.PullRequestRemoteRestpoint;
import com.atlassian.jira.plugins.dvcs.spi.bitbucket.dao.BitbucketPullRequestDao;

// TODO failure recovery + rename to stateful if will be stateful
public class BitbucketRepositoryActivitySynchronizer implements RepositoryActivitySynchronizer
{

    private final BitbucketClientRemoteFactory clientFactory;
    private final RepositoryActivityDao dao;
    private final RepositoryDao repositoryDao;
    private final PullRequestContextManager pullRequestContextManager;

    public BitbucketRepositoryActivitySynchronizer(BitbucketClientRemoteFactory clientFactory, RepositoryActivityDao dao,
            RepositoryDao repositoryDao, BitbucketPullRequestDao pullRequestDao)
    {
        super();
        this.clientFactory = clientFactory;
        this.dao = dao;
        this.repositoryDao = repositoryDao;
        this.pullRequestContextManager = new PullRequestContextManager(pullRequestDao, dao);
    }

    @Override
    public void synchronize(Repository forRepository, boolean softSync)
    {
        if (!softSync)
        {
            dao.removeAll(forRepository);
            forRepository.setActivityLastSync(null);
        }

        BitbucketRemoteClient remoteClient = clientFactory.getForRepository(forRepository, 2);
        PullRequestRemoteRestpoint pullRestpoint = remoteClient.getPullRequestAndCommentsRemoteRestpoint();

        //
        // get activities iterator
        //
        Iterable<BitbucketPullRequestActivityInfo> activities = pullRestpoint.getRepositoryActivity(forRepository.getOrgName(),
                forRepository.getSlug(), forRepository.getActivityLastSync());

        pullRequestContextManager.clear(forRepository);

        Date lastActivitySyncDate = forRepository.getActivityLastSync();

        Date previousActivityDate = null;
        //
        // check whether there's some interesting issue keys in activity
        // and persist it if yes
        //
        try
        {
            for (BitbucketPullRequestActivityInfo info : activities)
            {
                Date activityDate = ClientUtils.extractActivityDate(info.getActivity());
                if (lastActivitySyncDate == null)
                {
                    lastActivitySyncDate = activityDate;
                } else
                {
                    if (activityDate != null && activityDate.after(lastActivitySyncDate))
                    {
                        lastActivitySyncDate = activityDate;
                    }
                }

                // filtering duplicated activities in response
                // TODO implement better checking whether activity is duplicated than comparing dates
                if (!activityDate.equals(previousActivityDate))
                {
                    PullRequestContext pullRequestContext = pullRequestContextManager.getPullRequestContext(forRepository.getId(), info
                            .getPullRequest().getId());

                    processActivity(info, forRepository, pullRestpoint, pullRequestContext);

                    pullRequestContextManager.save(pullRequestContext);
                }
                previousActivityDate = activityDate;
            }

            for (PullRequestContext pullRequestContext : pullRequestContextManager.getPullRequestRequestContexts(forRepository.getId()))
            {
                // when soft sync, it could happen that we have no update activities and therefore no last update activity and iterator
                if (pullRequestContext.getLastUpdateActivity() != null)
                {
                    fillActivityCommits(forRepository, null, pullRequestContext);
                }

                dao.updatePullRequestIssueKeys(forRepository, pullRequestContext.getLocalPullRequestId());
            }
        } finally
        {
            pullRequestContextManager.clear(forRepository);
            repositoryDao.setLastActivitySyncDate(forRepository.getId(), lastActivitySyncDate);
        }
    }

    // -------------------------------------------------------------------------------------------------------
    // -------------------------------------------------------------------------------------------------------
    // Helpers ...
    // -------------------------------------------------------------------------------------------------------
    // -------------------------------------------------------------------------------------------------------

    private void processActivity(BitbucketPullRequestActivityInfo info, Repository forRepository, PullRequestRemoteRestpoint pullRestpoint,
            PullRequestContext pullRequestContext)
    {
        int localPullRequestId = ensurePullRequestPresent(forRepository, pullRestpoint, info, pullRequestContext);
        BitbucketPullRequestBaseActivity activity = info.getActivity();

        if (isUpdateActivity(activity))
        {
            pullRequestContext.setLastUpdateActivity((BitbucketPullRequestUpdateActivity) info.getActivity());
        } else
        {
            dao.saveActivity(forRepository, toDaoModel(info.getActivity(), forRepository.getId(), localPullRequestId));
        }
    }

    private boolean isUpdateActivity(BitbucketPullRequestBaseActivity activity)
    {
        return activity instanceof BitbucketPullRequestUpdateActivity
{'fraction_non_alphanumeric': 0.12472707423580787, 'fraction_numerical': 0.00034115720524017465, 'mean_word_length': 2.5601165897498177, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, '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': '6446455', 'n_tokens_mistral': 3511, 'n_tokens_neox': 3387, 'n_words': 557}