org.sigmah.offline.sync.Synchronizer.java Source code

Java tutorial

Introduction

Here is the source code for org.sigmah.offline.sync.Synchronizer.java

Source

package org.sigmah.offline.sync;

/*
 * #%L
 * Sigmah
 * %%
 * Copyright (C) 2010 - 2016 URD
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import java.util.List;
import java.util.Map;

import org.sigmah.client.dispatch.DispatchAsync;
import org.sigmah.offline.dao.UpdateDiaryAsyncDAO;
import org.sigmah.shared.command.GetHistory;
import org.sigmah.shared.command.GetProject;
import org.sigmah.shared.command.GetProjects;
import org.sigmah.shared.command.GetValue;
import org.sigmah.shared.command.base.Command;
import org.sigmah.shared.command.result.ListResult;
import org.sigmah.shared.command.result.ValueResult;
import org.sigmah.shared.dto.ProjectDTO;
import org.sigmah.shared.dto.element.FlexibleElementDTO;
import org.sigmah.shared.dto.history.HistoryTokenListDTO;

import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import org.sigmah.client.dispatch.CommandResultHandler;
import org.sigmah.client.i18n.I18N;
import org.sigmah.client.page.Page;
import org.sigmah.client.ui.notif.N10N;
import org.sigmah.client.ui.widget.Loadable;
import org.sigmah.offline.dao.FileDataAsyncDAO;
import org.sigmah.offline.dao.MonitoredPointAsyncDAO;
import org.sigmah.offline.dao.OrgUnitAsyncDAO;
import org.sigmah.offline.dao.ReminderAsyncDAO;
import org.sigmah.offline.dao.TransfertAsyncDAO;
import org.sigmah.shared.command.GetCalendar;
import org.sigmah.shared.command.GetOrgUnit;
import org.sigmah.shared.command.GetProjectReport;
import org.sigmah.shared.command.GetProjectReports;
import org.sigmah.shared.command.SecureNavigationCommand;
import org.sigmah.shared.command.Synchronize;
import org.sigmah.shared.command.result.Calendar;
import org.sigmah.shared.command.result.SecureNavigationResult;
import org.sigmah.shared.command.result.SynchronizeResult;
import org.sigmah.shared.command.result.VoidResult;
import org.sigmah.shared.dto.ProjectFundingDTO;
import org.sigmah.shared.dto.calendar.CalendarType;
import org.sigmah.shared.dto.calendar.PersonalCalendarIdentifier;
import org.sigmah.shared.dto.orgunit.OrgUnitDTO;
import org.sigmah.shared.dto.referential.ContainerInformation;
import org.sigmah.shared.dto.report.ProjectReportDTO;
import org.sigmah.shared.dto.report.ReportReference;

/**
 * Manage sync operations.
 * 
 * @author Raphal Calabro (rcalabro@ideia.fr)
 */
@Singleton
public class Synchronizer {

    private static final double ACCESS_RIGHTS_VALUE = 0.05;
    private static final double GET_PROJECT_VALUE = 0.1;
    private static final double GET_ORGUNIT_VALUE = 0.05;
    private static final double ORGUNIT_DETAIL_VALUE = 0.3;
    private static final double PROJECT_DETAIL_VALUE = 0.5;

    @Inject
    private UpdateDiaryAsyncDAO updateDiaryAsyncDAO;

    @Inject
    private ReminderAsyncDAO reminderAsyncDAO;

    @Inject
    private MonitoredPointAsyncDAO monitoredPointAsyncDAO;

    @Inject
    private TransfertAsyncDAO transfertAsyncDAO;

    @Inject
    private FileDataAsyncDAO fileDataAsyncDAO;

    @Inject
    private DispatchAsync dispatcher;

    @Inject
    private OrgUnitAsyncDAO orgUnitAsyncDAO;

    /**
    * Send to the server the modifications done offline.
    * 
    * @param callback Callback called when the push is finished or when an error occured.
    */
    public void push(final AsyncCallback<Void> callback) {
        final CommandQueue queue = new CommandQueue(callback, dispatcher);

        queue.add(new QueueEntry<Void>() {

            @Override
            public void run(final AsyncCallback<Void> callback, Loadable... loadables) {
                updateDiaryAsyncDAO.getAll(new AsyncCallback<Map<Integer, Command>>() {

                    @Override
                    public void onFailure(Throwable caught) {
                        callback.onFailure(caught);
                    }

                    @Override
                    public void onSuccess(final Map<Integer, Command> commands) {
                        queue.add(new Synchronize(new ArrayList(commands.values())),
                                new CommandResultHandler<SynchronizeResult>() {

                                    @Override
                                    protected void onCommandSuccess(SynchronizeResult result) {
                                        // Update local ids for files.
                                        transfertAsyncDAO.replaceIds(result.getFiles());
                                        fileDataAsyncDAO.replaceIds(result.getFiles());

                                        // Display erros.
                                        if (!result.getErrors().isEmpty()) {
                                            displayErrorNotification(result);
                                        }

                                        queue.add(new QueueEntry<VoidResult>() {

                                            @Override
                                            public void run(AsyncCallback<VoidResult> callback,
                                                    Loadable... loadables) {
                                                updateDiaryAsyncDAO.removeAll(commands.keySet(), callback);
                                            }
                                        });
                                    }
                                });

                        queue.add(new QueueEntry<VoidResult>() {

                            @Override
                            public void run(AsyncCallback<VoidResult> callback, Loadable... loadables) {
                                monitoredPointAsyncDAO.removeTemporaryObjects(callback);
                            }
                        });

                        queue.add(new QueueEntry<VoidResult>() {

                            @Override
                            public void run(AsyncCallback<VoidResult> callback, Loadable... loadables) {
                                reminderAsyncDAO.removeTemporaryObjects(callback);
                            }
                        });

                        callback.onSuccess(null);
                    }
                });
            }
        });

        queue.run();
    }

    /**
     * Store server data inside the local database.
     * 
     * @param progressListener Progress listener.
     */
    public void pull(final SynchroProgressListener progressListener) {
        pull(progressListener, false);
    }

    /**
     * Store server data inside the local database.
     * 
     * @param progressListener Progress listener.
     * @param withHistory <code>true</code> to retrieve history, <code>false</code> to ignore it.
     */
    public void pull(final SynchroProgressListener progressListener, final boolean withHistory) {
        final double[] progress = { 0.0 };

        // Called if the synchronization failed or when it is completed.
        final CommandQueue queue = new CommandQueue(new AsyncCallback<Void>() {

            @Override
            public void onFailure(Throwable caught) {
                progressListener.onFailure(caught);
            }

            @Override
            public void onSuccess(Void result) {
                progressListener.onComplete();
            }
        }, dispatcher);

        // Storing access rights
        final double pageAccessProgress = ACCESS_RIGHTS_VALUE / Page.values().length;
        for (final Page page : Page.values()) {
            queue.add(new SecureNavigationCommand(page), new CommandResultHandler<SecureNavigationResult>() {

                @Override
                protected void onCommandSuccess(SecureNavigationResult result) {
                    updateProgress(pageAccessProgress, progress, progressListener);
                }

            });
        }

        // Storing favorites projects
        final GetProjects getProjects = new GetProjects(Collections.<Integer>emptyList(),
                ProjectDTO.Mode.WITH_RELATED_PROJECTS);
        getProjects.setFavoritesOnly(true);
        queue.add(getProjects, new CommandResultHandler<ListResult<ProjectDTO>>() {

            @Override
            protected void onCommandSuccess(ListResult<ProjectDTO> result) {
                updateProgress(GET_PROJECT_VALUE, progress, progressListener);

                if (result == null || result.isEmpty()) {
                    // If nothing has been found.
                    updateProgress(PROJECT_DETAIL_VALUE, progress, progressListener);
                    return;
                }

                final HashSet<ProjectDTO> projects = new HashSet<ProjectDTO>();
                projects.addAll(result.getList());

                // Also fetching related projects
                for (final ProjectDTO project : result.getList()) {
                    for (final ProjectFundingDTO funding : project.getFunding()) {
                        projects.add(funding.getFunding());
                    }
                    for (final ProjectFundingDTO funded : project.getFunded()) {
                        projects.add(funded.getFunded());
                    }
                }

                final double projectProgress = PROJECT_DETAIL_VALUE / projects.size();

                for (final ProjectDTO project : projects) {
                    final Integer projectId = project.getId();

                    if (projectId != null) {
                        queue.add(new GetProject(projectId, null), new CommandResultHandler<ProjectDTO>() {

                            @Override
                            protected void onCommandSuccess(ProjectDTO result) {
                                if (result != null && result.getProjectModel() != null) {
                                    final List<FlexibleElementDTO> elements = result.getProjectModel()
                                            .getAllElements();
                                    queueDetails(queue, projectId, result.getCalendarId(), elements,
                                            projectProgress, progress, progressListener, withHistory);
                                } else {
                                    Log.warn("Project '" + projectId + "' was not found on the server.");
                                }
                            }
                        });
                    } else {
                        Log.warn("Null project id encountered while pulling data.");
                    }
                }
            }
        });

        // Storing org units data
        queue.add(new QueueEntry<Void>() {

            @Override
            public void run(final AsyncCallback<Void> callback, Loadable... loadables) {
                orgUnitAsyncDAO.getAll(new AsyncCallback<ListResult<OrgUnitDTO>>() {

                    @Override
                    public void onFailure(Throwable caught) {
                        callback.onFailure(caught);
                    }

                    @Override
                    public void onSuccess(final ListResult<OrgUnitDTO> result) {
                        updateProgress(GET_ORGUNIT_VALUE, progress, progressListener);

                        if (result == null || result.isEmpty()) {
                            // If nothing has been found.
                            updateProgress(ORGUNIT_DETAIL_VALUE, progress, progressListener);
                            return;
                        }

                        final double orgUnitProgress = ORGUNIT_DETAIL_VALUE / result.getSize();

                        for (final OrgUnitDTO orgUnit : result.getList()) {
                            if (orgUnit != null && orgUnit.getId() != null) {
                                final Integer orgUnitId = orgUnit.getId();

                                queue.add(new GetOrgUnit(orgUnitId, null), new CommandResultHandler<OrgUnitDTO>() {

                                    @Override
                                    protected void onCommandSuccess(OrgUnitDTO result) {
                                        // BUGFIX #795: Checking if an actual org unit exists for the given id.
                                        if (result != null && result.getOrgUnitModel() != null) {
                                            final List<FlexibleElementDTO> elements = result.getOrgUnitModel()
                                                    .getAllElements();
                                            queueDetails(queue, orgUnitId, result.getCalendarId(), elements,
                                                    orgUnitProgress, progress, progressListener, withHistory);
                                        } else {
                                            Log.warn("Cached org unit '" + orgUnitId
                                                    + "' was not found on the server.");
                                        }
                                    }
                                });
                            } else {
                                Log.warn("Null org unit encountered while pulling data.");
                            }
                        }

                        callback.onSuccess(null);
                    }
                });
            }
        });

        queue.run();
    }

    /**
     * Adds the operations required to download the details of a project/orgunit.
     * 
     * @param queue
      *          Command queue.
     * @param containerId
      *          Identifier of the project/orgunit.
     * @param calendarId
      *          Identifier of the calendar of the project/orgunit (may be null).
     * @param elements
      *          Full list of flexibles elements.
     * @param categoryProgress
      *          Percentage of progress for the given element.
     * @param progress
      *          Current progress.
     * @param progressListener
      *          Listener to call to update the progress bar.
     */
    private void queueDetails(final CommandQueue queue, int containerId, final Integer calendarId,
            List<FlexibleElementDTO> elements, double categoryProgress, final double[] progress,
            final SynchroProgressListener progressListener, final boolean withHistory) {

        final double flexibleElementCount = elements.size();
        final double historyCount = withHistory ? elements.size() : 0;
        final double calendarCount = calendarId != null ? 1.0 : 0.0;
        final double projectReports = 1.0;

        final double elementProgress = categoryProgress
                / (flexibleElementCount + historyCount + calendarCount + projectReports);

        // Fetching flexible elements and their history
        for (final FlexibleElementDTO element : elements) {
            // Caching element value
            final GetValue getValue = new GetValue(containerId, element.getId(), element.getEntityName());
            queue.add(getValue, new CommandResultHandler<ValueResult>() {
                @Override
                protected void onCommandSuccess(ValueResult result) {
                    // Success
                    updateProgress(elementProgress, progress, progressListener);
                }
            });

            // Caching value history
            if (withHistory) {
                final GetHistory getHistory = new GetHistory(element.getId(), containerId);
                queue.add(getHistory, new CommandResultHandler<ListResult<HistoryTokenListDTO>>() {
                    @Override
                    protected void onCommandSuccess(ListResult<HistoryTokenListDTO> result) {
                        // Success
                        updateProgress(elementProgress, progress, progressListener);
                    }
                });
            }
        }

        // Fetching the calendar
        if (calendarId != null) {
            final PersonalCalendarIdentifier identifier = new PersonalCalendarIdentifier(calendarId);
            queue.add(new GetCalendar(CalendarType.Personal, identifier), new CommandResultHandler<Calendar>() {

                @Override
                protected void onCommandSuccess(Calendar result) {
                    // Success
                    updateProgress(elementProgress, progress, progressListener);
                }
            });
        }

        // Fetching project reports
        queue.add(new GetProjectReports(containerId, null),
                new CommandResultHandler<ListResult<ReportReference>>() {

                    @Override
                    protected void onCommandSuccess(ListResult<ReportReference> result) {
                        if (result == null || result.isEmpty()) {
                            // No reports
                            updateProgress(elementProgress, progress, progressListener);

                        } else {
                            final double reportProgress = elementProgress / result.getSize();

                            // Fetching actuals reports
                            for (final ReportReference reportReference : result.getList()) {
                                queue.add(new GetProjectReport(reportReference.getId()),
                                        new CommandResultHandler<ProjectReportDTO>() {

                                            @Override
                                            protected void onCommandSuccess(ProjectReportDTO result) {
                                                // Success
                                                updateProgress(reportProgress, progress, progressListener);
                                            }
                                        });
                            }
                        }
                    }
                });
    }

    /**
     * Update the current progress and refresh the progress bar.
     * 
     * @param progress
      *          Progression.
     * @param total
      *          Total progression.
     * @param progressListener
      *          Listener to call to refresh the progress bar.
     */
    private void updateProgress(double progress, double[] total, SynchroProgressListener progressListener) {

        total[0] += progress;
        if (total[0] > 1.0) {
            total[0] = 1.0;
        }
        progressListener.onProgress(total[0]);
    }

    /**
     * Display an error message built from the returned errors.
     * 
     * @param result 
     *          Result of a synchronization.
     */
    private void displayErrorNotification(final SynchronizeResult result) {

        final SafeHtmlBuilder errorBuilder = new SafeHtmlBuilder();

        for (final Map.Entry<ContainerInformation, List<String>> entry : result.getErrors().entrySet()) {
            final ContainerInformation information = entry.getKey();

            if (information.isProject()) {
                errorBuilder.appendHtmlConstant(I18N.CONSTANTS.project());
            } else {
                errorBuilder.appendHtmlConstant(I18N.CONSTANTS.orgunit());
            }

            errorBuilder.appendHtmlConstant(" ").appendEscaped(information.getName()).appendHtmlConstant(" - ")
                    .appendEscaped(information.getFullName())
                    .appendHtmlConstant("<ul style=\"margin:0.5em 0 1em 1.5em;list-style:disc\">");

            for (final String error : entry.getValue()) {
                errorBuilder.appendHtmlConstant("<li>").appendEscapedLines(error).appendHtmlConstant("</li>");
            }
            errorBuilder.appendHtmlConstant("</ul>");
        }

        if (result.isErrorConcernFiles()) {
            errorBuilder.appendHtmlConstant(I18N.MESSAGES.conflictFiles());
        }

        errorBuilder.appendHtmlConstant(I18N.MESSAGES.conflictSentByMail());

        N10N.error(errorBuilder.toSafeHtml().asString());
    }
}