package org.sigmah.client.ui.presenter.importation; /* * #%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.extjs.gxt.ui.client.data.BaseModelData; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.ButtonEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.FormEvent; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.event.SelectionListener; import com.extjs.gxt.ui.client.store.ListStore; import com.extjs.gxt.ui.client.widget.form.ComboBox; import com.extjs.gxt.ui.client.widget.form.Field; import com.extjs.gxt.ui.client.widget.form.FileUploadField; import com.extjs.gxt.ui.client.widget.form.FormPanel; import com.extjs.gxt.ui.client.widget.form.FormPanel.Method; import com.extjs.gxt.ui.client.widget.form.Radio; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.http.client.Response; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.Widget; import com.google.inject.ImplementedBy; import com.google.inject.Inject; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.sigmah.client.dispatch.CommandResultHandler; import org.sigmah.client.dispatch.monitor.LoadingMask; import org.sigmah.client.event.EventBus; import org.sigmah.client.event.UpdateEvent; import org.sigmah.client.event.handler.UpdateHandler; import org.sigmah.client.i18n.I18N; import org.sigmah.client.inject.Injector; import org.sigmah.client.page.Page; import org.sigmah.client.page.PageRequest; import org.sigmah.client.page.RequestParameter; import org.sigmah.client.ui.notif.N10N; import org.sigmah.client.ui.presenter.CreateProjectPresenter; import org.sigmah.client.ui.presenter.base.AbstractPagePresenter; import org.sigmah.client.ui.presenter.base.HasForm; import org.sigmah.client.ui.presenter.project.AbstractProjectPresenter; import org.sigmah.client.ui.view.importation.AutomatedImportResultPopup; import org.sigmah.client.ui.view.importation.ElementExtractedValuePopup; import org.sigmah.client.ui.view.importation.ImportDetailsPopup; import org.sigmah.client.ui.view.importation.ImportationView; import org.sigmah.client.ui.widget.button.Button; import org.sigmah.client.ui.widget.form.Forms; import org.sigmah.offline.fileapi.Blob; import org.sigmah.shared.command.AmendmentActionCommand; import org.sigmah.shared.command.AutomatedImport; import org.sigmah.shared.command.BatchCommand; import org.sigmah.shared.command.GetImportInformation; import org.sigmah.shared.command.GetImportationSchemes; import org.sigmah.shared.command.UpdateProject; import org.sigmah.shared.command.result.ImportInformationResult; import org.sigmah.shared.command.result.ListResult; import org.sigmah.shared.command.result.Result; import org.sigmah.shared.dto.ElementExtractedValue; import org.sigmah.shared.dto.ImportDetails; import org.sigmah.shared.dto.ProjectDTO; import org.sigmah.shared.dto.base.EntityDTO; import org.sigmah.shared.dto.element.BudgetElementDTO; import org.sigmah.shared.dto.element.BudgetSubFieldDTO; import org.sigmah.shared.dto.element.DefaultFlexibleElementContainer; import org.sigmah.shared.dto.element.DefaultFlexibleElementDTO; import org.sigmah.shared.dto.element.event.ValueEvent; import org.sigmah.shared.dto.importation.ImportationSchemeDTO; import org.sigmah.shared.dto.orgunit.OrgUnitDTO; import org.sigmah.shared.dto.referential.AmendmentAction; import org.sigmah.shared.dto.referential.BudgetSubFieldType; import org.sigmah.shared.dto.referential.DefaultFlexibleElementType; import org.sigmah.shared.dto.referential.ImportStatusCode; import org.sigmah.shared.dto.referential.ProjectModelStatus; import org.sigmah.shared.servlet.ServletConstants; import org.sigmah.shared.servlet.ServletUrlBuilder; /** * Data import presenter. * * Based on work done by Guerline Jean-Baptiste. * * @author Raphaƫl Calabro (rcalabro@ideia.fr) */ public class ImportationPresenter extends AbstractPagePresenter<ImportationPresenter.View> { /** * Description of the view managed by this presenter. */ @ImplementedBy(ImportationView.class) public static interface View extends AbstractProjectPresenter.View, HasForm { Field<ImportationSchemeDTO> getSchemeField(); FileUploadField getFileField(); Field<Boolean> getAutomatedField(); Field<Boolean> getNewProjectsPolicyField(); Field<Boolean> getProjectCorePolicyField(); Radio getMultipleMatchPolicyField(); Button getImportButton(); ImportDetailsPopup getImportDetailsPopup(); ElementExtractedValuePopup getElementExtractedValuePopup(); AutomatedImportResultPopup getAutomatedImportResultPopup(); ListStore<ImportationSchemeDTO> getSchemeListStore(); void hide(); } private HandlerRegistration handlerRegistration; private final Map<Integer, List<ElementExtractedValue>> changes; private String fileName; private String lastFileId; /** * Presenters's initialization. * * @param view * Presenter's view interface. * @param injector * Injected client injector. */ @Inject public ImportationPresenter(View view, Injector injector) { super(view, injector); changes = new HashMap<Integer, List<ElementExtractedValue>>(); } /** * {@inheritDoc} */ @Override public Page getPage() { return Page.IMPORT_VALUES; } /** * {@inheritDoc} */ @Override public void onBind() { setPageTitle(I18N.CONSTANTS.importItem()); // -- // Action when submitting the form. // -- final ServletUrlBuilder servletUrlBuilder = new ServletUrlBuilder(injector.getAuthenticationProvider(), injector.getPageManager(), ServletConstants.Servlet.IMPORT, ServletConstants.ServletMethod.IMPORT_STORE_FILE); final FormPanel form = view.getForms()[0]; form.setEncoding(FormPanel.Encoding.MULTIPART); form.setMethod(Method.POST); form.setAction(servletUrlBuilder.toString()); form.addListener(Events.Submit, new Listener<FormEvent>() { @Override public void handleEvent(FormEvent be) { onSubmit(be.getResultHtml()); } }); // -- // Action of the import button. // -- view.getImportButton().addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { view.getForms()[0].submit(); final Blob blob = Blob.getBlobFromInputFileElement(view.getFileField().getFileInput()); if(blob != null) { fileName = blob.getName(); } } }); // -- // Activation rule of the import button. // -- view.getSchemeField().addListener(Events.Select, new Listener<BaseEvent>() { @Override public void handleEvent(BaseEvent be) { view.getImportButton().setEnabled(view.getSchemeField().getValue() != null); } }); // -- // Visibility of the automated options. // -- view.getAutomatedField().addListener(Events.Change, new Listener<BaseEvent>() { @Override public void handleEvent(BaseEvent be) { final boolean enabled = view.getAutomatedField().getValue(); view.getNewProjectsPolicyField().setEnabled(enabled); view.getProjectCorePolicyField().setEnabled(enabled); view.getMultipleMatchPolicyField().getGroup().setEnabled(enabled); } }); // -- // Import details popup. // -- final ImportDetailsPopup popup = view.getImportDetailsPopup(); popup.setActionRenderer(new ActionRenderer() { @Override public Widget renderActionsForModel(ImportDetails model) { switch(model.getEntityStatus()) { case PROJECT_NOT_FOUND_CODE: return renderCreateButton(model); case PROJECT_FOUND_CODE: case ORGUNIT_FOUND_CODE: addAllChanges(model); popup.addModelToSelection(model); return renderConfirmButton(model); case PROJECT_LOCKED_CODE: return renderUnlockButton(model); case SEVERAL_ORGUNITS_FOUND_CODE: case SEVERAL_PROJECTS_FOUND_CODE: return renderChoosePanel(model); default: return null; } } }); popup.getImportButton().addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { onImport(); // Closing the file selection popup. view.hide(); } }); // -- // Extracted value popup. // -- final ElementExtractedValuePopup elementExtractedValuePopup = view.getElementExtractedValuePopup(); elementExtractedValuePopup.getConfirmButton().addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { final List<ElementExtractedValue> selection = elementExtractedValuePopup.getSelection(); changes.put((Integer)elementExtractedValuePopup.getEntity().getId(), selection); if (!selection.isEmpty()) { view.getImportDetailsPopup().getGrid().getSelectionModel().select(elementExtractedValuePopup.getParentModel(), true); } else { view.getImportDetailsPopup().getGrid().getSelectionModel().deselect(elementExtractedValuePopup.getParentModel()); } elementExtractedValuePopup.hide(); } }); // -- // Automated import result popup. // -- view.getAutomatedImportResultPopup().getCloseButton().addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { view.getAutomatedImportResultPopup().hide(); } }); } /** * {@inheritDoc} */ @Override public void onPageRequest(PageRequest request) { this.lastFileId = null; this.fileName = null; view.getSchemeListStore().removeAll(); view.getSchemeField().clear(); if(view.getFileField().isRendered()) { view.getFileField().clear(); } changes.clear(); dispatch.execute(new GetImportationSchemes(), new CommandResultHandler<ListResult<ImportationSchemeDTO>>() { @Override protected void onCommandSuccess(ListResult<ImportationSchemeDTO> result) { view.getSchemeListStore().add(result.getList()); } }); view.getImportButton().disable(); // Register events. registerEvents(); } /** * {@inheritDoc} */ @Override public void beforeLeaving(EventBus.LeavingCallback callback) { // Stop listener to creation events. deregisterEvents(); callback.leavingOk(); } // -- // Event handling. // -- private void registerEvents() { handlerRegistration = eventBus.addHandler(UpdateEvent.getType(), new UpdateHandler() { @Override public void onUpdate(UpdateEvent event) { if(event.concern(UpdateEvent.PROJECT_CREATE)) { final ListStore<ImportDetails> store = view.getImportDetailsPopup().getStore(); final ProjectDTO project = event.getParam(1); ImportDetails toBeRemoved = null; for(final ImportDetails importDetails : store.getModels()) { // TODO: KeyIdentification may be something other than the code. if(project.getName().equals(importDetails.getKeyIdentification()) && project.getProjectModel().getName().equals(importDetails.getModelName())) { toBeRemoved = importDetails; } } store.remove(toBeRemoved); store.commitChanges(); // BUGFIX #774: Reloading results to take created projects in account. refreshResults(); } } }); } private void deregisterEvents() { handlerRegistration.removeHandler(); handlerRegistration = null; } // -- // Utility methods. // -- private void onSubmit(String result) { switch (ServletConstants.getErrorCode(result)) { case Response.SC_OK: if (view.getAutomatedField().getValue()) { doAutomatedImport(result); } else { loadImportResults(result); } break; default: N10N.error(I18N.CONSTANTS.createFormIncomplete(), I18N.MESSAGES.importFormIncompleteDetails("")); break; } } private void doAutomatedImport(String fileId) { dispatch.execute(new AutomatedImport(fileId, fileName, view.getSchemeField().getValue(), view.getNewProjectsPolicyField().getValue(), view.getProjectCorePolicyField().getValue(), view.getMultipleMatchPolicyField().getValue()), new CommandResultHandler<ListResult<BaseModelData>>() { @Override protected void onCommandSuccess(ListResult<BaseModelData> result) { final AutomatedImportResultPopup popup = view.getAutomatedImportResultPopup(); final ListStore<BaseModelData> store = popup.getStore(); store.removeAll(); store.add(result.getData()); popup.show(); view.hide(); } }, view.getImportButton()); } private void loadImportResults(String fileId) { this.lastFileId = fileId; dispatch.execute(new GetImportInformation(fileId, view.getSchemeField().getValue()), new CommandResultHandler<ImportInformationResult>() { @Override protected void onCommandSuccess(ImportInformationResult result) { // Shows result popup. final ImportDetailsPopup popup = view.getImportDetailsPopup(); popup.getStore().removeAll(); popup.getStore().add(result.getEntitiesToImport()); popup.center(); } }, view.getImportButton()); } private void refreshResults() { if(lastFileId != null) { final ImportDetailsPopup popup = view.getImportDetailsPopup(); dispatch.execute(new GetImportInformation(lastFileId, view.getSchemeField().getValue()), new CommandResultHandler<ImportInformationResult>() { @Override protected void onCommandSuccess(ImportInformationResult result) { popup.getStore().removeAll(); popup.getStore().add(result.getEntitiesToImport()); } }, view.getImportButton(), new LoadingMask(popup.getGrid())); } } private Button renderCreateButton(final ImportDetails model) { final Button createButton = Forms.button(I18N.CONSTANTS.createProjectCreateButton()); createButton.addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { final Map<EntityDTO<Integer>, List<ElementExtractedValue>> entities = model.getEntitiesToImport(); final Iterator<EntityDTO<Integer>> iterator = entities.keySet().iterator(); final EntityDTO<?> notFoundEntity = iterator.next(); // Prepare the page request and select the creation type based // on the status of the project model. final PageRequest request = Page.CREATE_PROJECT.requestWith( RequestParameter.TYPE, model.getModelStatus() == ProjectModelStatus.DRAFT ? CreateProjectPresenter.Mode.TEST_PROJECT : CreateProjectPresenter.Mode.PROJECT ); // Pre-defines the extracted values in the create project popup. final List<ElementExtractedValue> extractedValues = entities.get(notFoundEntity); for(final ElementExtractedValue extractedValue : extractedValues) { // -- // Budget. // -- if(extractedValue.getElement() instanceof BudgetElementDTO) { final BudgetElementDTO element = (BudgetElementDTO)extractedValue.getElement(); for (BudgetSubFieldDTO budgetSubField : element.getBudgetSubFields()) { final Double value = (Double) extractedValue.getNewBudgetValues().get(budgetSubField.getId()); if(budgetSubField.getType() == BudgetSubFieldType.PLANNED && value != null) { request.addData(RequestParameter.BUDGET, value); } } } else if(extractedValue.getElement() instanceof DefaultFlexibleElementDTO) { final DefaultFlexibleElementDTO element = (DefaultFlexibleElementDTO)extractedValue.getElement(); // -- // Code. // -- if(element.getType() == DefaultFlexibleElementType.CODE) { request.addData(RequestParameter.CODE, extractedValue.getNewValue()); // -- // Title. // -- } else if(element.getType() == DefaultFlexibleElementType.TITLE) { request.addData(RequestParameter.TITLE, extractedValue.getNewValue()); } } } // -- // Project Model. // -- request.addData(RequestParameter.MODEL, model.getModelName()); eventBus.navigateRequest(request, createButton); } }); return createButton; } private void addAllChanges(final ImportDetails model) { final Map<EntityDTO<Integer>, List<ElementExtractedValue>> entities = model.getEntitiesToImport(); if(!entities.keySet().isEmpty()) { final Iterator<EntityDTO<Integer>> iterator = entities.keySet().iterator(); final EntityDTO<Integer> entity = iterator.next(); changes.put(entity.getId(), entities.get(entity)); } } private Button renderConfirmButton(final ImportDetails model) { final Button confirmButton = Forms.button(I18N.CONSTANTS.importButtonConfirmDetails()); confirmButton.addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { final Map<EntityDTO<Integer>, List<ElementExtractedValue>> entities = model.getEntitiesToImport(); if(!entities.keySet().isEmpty()) { // Displaying the "select changes" popup. final ElementExtractedValuePopup popup = view.getElementExtractedValuePopup(); final Iterator<EntityDTO<Integer>> iterator = entities.keySet().iterator(); final EntityDTO<?> entity = iterator.next(); popup.setEntity(entity); popup.setParentModel(model); popup.getStore().removeAll(); popup.getStore().add(entities.get(entity)); popup.getSelectionModel().select(changes.get((Integer)entity.getId()), false); popup.center(); } } }); return confirmButton; } private Button renderUnlockButton(final ImportDetails model) { final Button unlockButton = Forms.button(I18N.CONSTANTS.projectCoreUnlockButton()); unlockButton.addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { final Iterator<EntityDTO<Integer>> iterator = model.getEntitiesToImport().keySet().iterator(); final EntityDTO<Integer> entityLocked = iterator.next(); dispatch.execute(new AmendmentActionCommand(entityLocked.getId(), AmendmentAction.UNLOCK), new CommandResultHandler<ProjectDTO>() { @Override protected void onCommandSuccess(ProjectDTO result) { model.setEntityStatus(ImportStatusCode.PROJECT_FOUND_CODE); final ImportDetailsPopup popup = view.getImportDetailsPopup(); popup.addModelToSelection(model); popup.getStore().update(model); popup.getStore().commitChanges(); } }, unlockButton); } }); return unlockButton; } private Panel renderChoosePanel(final ImportDetails model) { // Project/orgUnit combobox. final ComboBox<DefaultFlexibleElementContainer> comboBox = Forms.combobox(null, false, ProjectDTO.ID, ProjectDTO.NAME, I18N.CONSTANTS.formWindowListEmptyText()); for(final EntityDTO<?> entity : model.getEntitiesToImport().keySet()) { comboBox.getStore().add((DefaultFlexibleElementContainer) entity); } // Choose button. final Button chooseButton = Forms.button(I18N.CONSTANTS.importButtonChoose()); chooseButton.addSelectionListener(new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { final DefaultFlexibleElementContainer entity = comboBox.getValue(); if (entity != null) { // Rebuild the "entitiesToImport" map. final List<ElementExtractedValue> extractedValues = model.getEntitiesToImport().get(entity); final HashMap<EntityDTO<Integer>, List<ElementExtractedValue>> entitiesToImport = new HashMap<EntityDTO<Integer>, List<ElementExtractedValue>>(); entitiesToImport.put(entity, extractedValues); // Update the importDetails. model.setEntitiesToImport(entitiesToImport); model.setEntityStatus(entity instanceof ProjectDTO ? ImportStatusCode.PROJECT_FOUND_CODE : ImportStatusCode.ORGUNIT_FOUND_CODE); // Update the grid. view.getImportDetailsPopup().getGrid().getSelectionModel().select(model, true); view.getImportDetailsPopup().getStore().update(model); view.getImportDetailsPopup().getStore().commitChanges(); } } }); // Creating the panel. final HorizontalPanel panel = new HorizontalPanel(); panel.setSpacing(5); panel.add(comboBox); panel.add(chooseButton); return panel; } /** * Apply all the selected changes. */ private void onImport() { final List<ImportDetails> selection = view.getImportDetailsPopup().getSelection(); if(!selection.isEmpty()) { final BatchCommand updates = new BatchCommand(); final ArrayList<String> names = new ArrayList<String>(); for(final ImportDetails importDetails : selection) { if(ImportStatusCode.isFound(importDetails.getEntityStatus()) && !importDetails.getEntitiesToImport().isEmpty()) { // Prepare an UpdateProject command for this ImportDetails object. final Iterator<EntityDTO<Integer>> iterator = importDetails.getEntitiesToImport().keySet().iterator(); final EntityDTO<Integer> selectedEntity = iterator.next(); final List<ValueEvent> values = toValueEvent(changes.get(selectedEntity.getId())); // Save the name of the project (to notify that this project has been updated). if (selectedEntity instanceof ProjectDTO) { names.add(((ProjectDTO)selectedEntity).getFullName()); } else if(selectedEntity instanceof OrgUnitDTO) { names.add(((OrgUnitDTO)selectedEntity).getFullName()); } // Add the update project to the batch. // TODO: I18N updates.add(new UpdateProject(selectedEntity.getId(), values, "Imported from file '" + fileName + "'.")); } } updates.add(new GetImportInformation(fileName, true)); if(!updates.getCommands().isEmpty()) { dispatch.execute(updates, new CommandResultHandler<ListResult<Result>>() { @Override protected void onCommandSuccess(ListResult<Result> result) { // Display a "save success" notification for each modified project. for(final String name : names) { N10N.infoNotif(I18N.CONSTANTS.importItem(), I18N.MESSAGES.importSuccessful(name)); } view.getImportDetailsPopup().hide(); } }, view.getImportDetailsPopup().getImportButton()); } } else { // Warn the user that no change has been selected. N10N.warn(I18N.CONSTANTS.importItem(), I18N.CONSTANTS.importDetailsWindowSelectionEmpty()); } } /** * Convert every given {@link ElementExtractedValue} to an instance of {@link ValueEvent}. * * @param extractedValues <code>ElementExtractedValue</code> to convert. * @return A list of <code>ValueEvent</code>. */ private List<ValueEvent> toValueEvent(List<ElementExtractedValue> extractedValues) { final ArrayList<ValueEvent> valueEvents = new ArrayList<ValueEvent>(); for(final ElementExtractedValue extractedValue : extractedValues) { final ValueEvent valueEvent = extractedValue.toValueEvent(); if (valueEvent != null) { valueEvents.add(valueEvent); } } return valueEvents; } }