package org.sigmah.offline.handler; /* * #%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 org.sigmah.client.dispatch.DispatchListener; import org.sigmah.offline.dao.RequestManager; import org.sigmah.offline.dao.RequestManagerCallback; import org.sigmah.offline.dao.UpdateDiaryAsyncDAO; import org.sigmah.offline.dao.ValueAsyncDAO; import org.sigmah.offline.dispatch.AsyncCommandHandler; import org.sigmah.offline.dispatch.OfflineExecutionContext; import org.sigmah.offline.js.ValueJSIdentifierFactory; import org.sigmah.shared.command.UpdateProject; import org.sigmah.shared.command.result.ValueResult; import org.sigmah.shared.command.result.VoidResult; import org.sigmah.shared.dto.element.event.ValueEventWrapper; import com.allen_sauer.gwt.log.client.Log; import com.google.gwt.user.client.Timer; 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.List; import org.sigmah.client.computation.ClientValueResolver; import org.sigmah.client.i18n.I18N; import org.sigmah.offline.dao.ComputationAsyncDAO; import org.sigmah.offline.dao.ProjectAsyncDAO; import org.sigmah.offline.sync.SuccessCallback; import org.sigmah.shared.command.result.Authentication; import org.sigmah.shared.computation.Computation; import org.sigmah.shared.computation.value.ComputedValue; import org.sigmah.shared.computation.value.ComputedValues; import org.sigmah.shared.dispatch.UpdateConflictException; import org.sigmah.shared.dto.ProjectDTO; import org.sigmah.shared.dto.ProjectFundingDTO; import org.sigmah.shared.dto.ProjectModelDTO; import org.sigmah.shared.dto.element.ComputationElementDTO; import org.sigmah.shared.dto.element.FlexibleElementDTO; import org.sigmah.shared.dto.referential.ContainerInformation; import org.sigmah.shared.util.Collections; /** * JavaScript implementation of {@link org.sigmah.server.handler.UpdateProjectHandler}. * Used when the user is offline. * * @author Raphaƫl Calabro (rcalabro@ideia.fr) */ @Singleton public class UpdateProjectAsyncHandler implements AsyncCommandHandler<UpdateProject, VoidResult>, DispatchListener<UpdateProject, VoidResult> { @Inject private ValueAsyncDAO valueAsyncDAO; @Inject private UpdateDiaryAsyncDAO updateDiaryAsyncDAO; @Inject private ProjectAsyncDAO projectAsyncDAO; @Inject private ComputationAsyncDAO computationAsyncDAO; @Inject private ClientValueResolver clientValueResolver; /** * {@inheritDoc} */ @Override public void execute(final UpdateProject command, final OfflineExecutionContext executionContext, final AsyncCallback<VoidResult> callback) { checkComputations(command.getValues(), command.getProjectId(), new SuccessCallback<ProjectDTO>(callback) { @Override public void onSuccess(final ProjectDTO project) { saveValues(command, new SuccessCallback<VoidResult>(callback) { @Override public void onSuccess(VoidResult result) { updateImpactedComputations(command, project, callback); } }); } }); } /** * {@inheritDoc} */ @Override public void onSuccess(final UpdateProject command, VoidResult result, Authentication authentication) { // Updating local database for (final ValueEventWrapper valueEventWrapper : command.getValues()) { final String id = ValueJSIdentifierFactory.toIdentifier(command, valueEventWrapper); valueAsyncDAO.get(id, new AsyncCallback<ValueResult>() { @Override public void onFailure(Throwable caught) { Log.warn("Error while updating local database for element '" + id + "'.", caught); } @Override public void onSuccess(ValueResult result) { valueAsyncDAO.saveOrUpdate(command, valueEventWrapper, result, null); } }); } } private void saveValues(final UpdateProject command, final AsyncCallback<VoidResult> callback) { // Updating the local database final RequestManager<VoidResult> requestManager = new RequestManager<VoidResult>(null, callback); for (final ValueEventWrapper valueEventWrapper : command.getValues()) { final String id = ValueJSIdentifierFactory.toIdentifier(command, valueEventWrapper); valueAsyncDAO.get(id, new RequestManagerCallback<VoidResult, ValueResult>(requestManager) { @Override public void onRequestSuccess(ValueResult result) { valueAsyncDAO.saveOrUpdate(command, valueEventWrapper, result, new RequestManagerCallback<VoidResult, VoidResult>(requestManager) { @Override public void onRequestSuccess(VoidResult result) { // Delay the callback to allow IndexedDB to cleanly // close its transaction. final int delayId = requestManager.prepareRequest(); new Timer() { @Override public void run() { requestManager.setRequestSuccess(delayId); } }.schedule(100); } }); } }); } // Saving the action in the local database updateDiaryAsyncDAO.saveOrUpdate(command); requestManager.ready(); } /** * Update the value of the computations impacted by the changes. * * @param command * Instance of <code>UpdateProject</code> command that contains the modifications. * @param project * Project being updated. * @param callback * Called when the computations are done. */ private void updateImpactedComputations(final UpdateProject command, final ProjectDTO project, final AsyncCallback<VoidResult> callback) { final RequestManager<VoidResult> requestManager = new RequestManager<VoidResult>(null, callback); for (final ValueEventWrapper valueEventWrapper : command.getValues()) { computationAsyncDAO.get(valueEventWrapper.getSourceElement(), new RequestManagerCallback<VoidResult, List<ComputationElementDTO>>(requestManager) { @Override public void onRequestSuccess(final List<ComputationElementDTO> result) { final ArrayList<ProjectFundingDTO> allFundings = new ArrayList<ProjectFundingDTO>(); allFundings.addAll(project.getFunded()); allFundings.addAll(project.getFunding()); for (final ComputationElementDTO computationElement : result) { final ProjectModelDTO parentModel = computationElement.getProjectModel(); if (parentModel != null) { final Computation computation = computationElement.getComputationForModel(parentModel); final Integer parentModelId = parentModel.getId(); for (final ProjectFundingDTO projectFunding : allFundings) { final ProjectDTO fundedProject = projectFunding.getFunded(); if (parentModelId.equals(fundedProject.getProjectModel().getId())) { updateComputationValueForProject(computationElement, computation, fundedProject, command, requestManager); } final ProjectDTO fundingProject = projectFunding.getFunding(); if (parentModelId.equals(fundingProject.getProjectModel().getId())) { updateComputationValueForProject(computationElement, computation, fundingProject, command, requestManager); } } } } } }); } requestManager.ready(); } /** * Update the value of the given computation for the given project. * * @param computationElement * Element to update. * @param computation * Formula of the computation. * @param project * Project to update. * @param command * Instance of <code>UpdateProject</code> command that contains the modifications. * @param requestManager * Request manager to use. */ private void updateComputationValueForProject(final ComputationElementDTO computationElement, final Computation computation, final ProjectDTO project, final UpdateProject command, final RequestManager<VoidResult> requestManager) { computation.computeValueWithWrappersAndResolver(project.getId(), command.getValues(), clientValueResolver, new RequestManagerCallback<VoidResult, String>(requestManager) { @Override public void onRequestSuccess(String result) { valueAsyncDAO.saveOrUpdate(result, computationElement, project.getId(), new RequestManagerCallback<VoidResult, VoidResult>(requestManager) { @Override public void onRequestSuccess(VoidResult result) { // Success. } }); } }); } /** * Retrieves the project from the local database and begin to check for conflicts. * * @param valueEvents * List of modifications. * @param projectId * Identifier of the modified project. */ private void checkComputations(final List<ValueEventWrapper> valueEvents, final int projectId, final AsyncCallback<ProjectDTO> callback) { projectAsyncDAO.get(projectId, new SuccessCallback<ProjectDTO>(callback) { @Override public void onSuccess(final ProjectDTO project) { try { checkComputations(valueEvents, project); callback.onSuccess(project); } catch (UpdateConflictException e) { onFailure(e); } } }); } /** * Search for computations and verify if the value matches the constraints of the field. * * @param valueEvents * List of changes. */ private void checkComputations(final List<ValueEventWrapper> valueEvents, final ProjectDTO project) throws UpdateConflictException { final List<String> conflicts = new ArrayList<String>(); for (final ValueEventWrapper valueEvent : valueEvents) { final FlexibleElementDTO source = valueEvent.getSourceElement(); if (source instanceof ComputationElementDTO && ((ComputationElementDTO) source).hasConstraints()) { checkComputation((ComputationElementDTO) source, ComputedValues.from(valueEvent.getSingleValue()), project, valueEvents, conflicts); } } if (!conflicts.isEmpty()) { // At least one conflict was found. throw new UpdateConflictException( new ContainerInformation(project.getId(), project.getName(), project.getFullName(), true), conflicts.toArray(new String[conflicts.size()])); } } /** * Check if the new value of the given computation exceeds one of its constraints. * * @param computationElement * Modified computation field. * @param value * New value. * @param project * Modified project. * @param valueEvents * List of modifications. * @param conflicts * List of conflicts where to add the result of this verification. */ private void checkComputation(final ComputationElementDTO computationElement, final ComputedValue value, final ProjectDTO project, final List<ValueEventWrapper> valueEvents, final List<String> conflicts) { final int comparison = value.matchesConstraints(computationElement); if (comparison != 0) { final String greaterOrLess, breachedConstraint; if (comparison < 0) { greaterOrLess = I18N.CONSTANTS.flexibleElementComputationLess(); breachedConstraint = computationElement.getMinimumValue(); } else { greaterOrLess = I18N.CONSTANTS.flexibleElementComputationGreater(); breachedConstraint = computationElement.getMaximumValue(); } final Computation computation = computationElement.getComputationForModel(project.getProjectModel()); final List<ValueEventWrapper> changes = computation.getRelatedChanges(valueEvents); final String fieldList = Collections.join(changes, new Collections.Mapper<ValueEventWrapper, String>() { @Override public String forEntry(ValueEventWrapper entry) { return entry.getSourceElement().getFormattedLabel(); } }, ", "); conflicts.add(I18N.MESSAGES.conflictComputationOutOfBoundOffline(fieldList, value.toString(), computationElement.getFormattedLabel(), greaterOrLess, breachedConstraint)); } } }