/* * Copyright (C) 2011 Jan Pokorsky * * 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/>. */ package cz.cas.lib.proarc.webapp.client.presenter; import com.google.gwt.place.shared.PlaceController; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.smartgwt.client.data.Criteria; import com.smartgwt.client.data.DSCallback; import com.smartgwt.client.data.DSRequest; import com.smartgwt.client.data.DSResponse; import com.smartgwt.client.data.Record; import com.smartgwt.client.types.PromptStyle; import com.smartgwt.client.util.BooleanCallback; import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.Canvas; import com.smartgwt.client.widgets.layout.VLayout; import com.smartgwt.client.widgets.toolbar.ToolStrip; import cz.cas.lib.proarc.webapp.client.ClientMessages; import cz.cas.lib.proarc.webapp.client.ClientUtils; import cz.cas.lib.proarc.webapp.client.ErrorHandler; import cz.cas.lib.proarc.webapp.client.action.AbstractAction; import cz.cas.lib.proarc.webapp.client.action.Action; import cz.cas.lib.proarc.webapp.client.action.ActionEvent; import cz.cas.lib.proarc.webapp.client.action.Actions; import cz.cas.lib.proarc.webapp.client.action.Actions.ActionSource; import cz.cas.lib.proarc.webapp.client.action.DigitalObjectFormValidateAction; import cz.cas.lib.proarc.webapp.client.action.DigitalObjectFormValidateAction.Validatable; import cz.cas.lib.proarc.webapp.client.action.RefreshAction; import cz.cas.lib.proarc.webapp.client.ds.ImportBatchDataSource; import cz.cas.lib.proarc.webapp.client.ds.ImportBatchDataSource.BatchRecord; import cz.cas.lib.proarc.webapp.client.ds.ImportBatchItemDataSource; import cz.cas.lib.proarc.webapp.client.ds.ImportTreeDataSource; import cz.cas.lib.proarc.webapp.client.ds.ImportTreeDataSource.ImportRecord; import cz.cas.lib.proarc.webapp.client.ds.RelationDataSource; import cz.cas.lib.proarc.webapp.client.ds.RestConfig; import cz.cas.lib.proarc.webapp.client.presenter.DigitalObjectManaging.DigitalObjectManagerPlace; import cz.cas.lib.proarc.webapp.client.presenter.Importing.ImportPlace; import cz.cas.lib.proarc.webapp.client.presenter.Importing.ImportPlace.Type; import cz.cas.lib.proarc.webapp.client.widget.ImportBatchChooser; import cz.cas.lib.proarc.webapp.client.widget.ImportBatchChooser.ImportBatchChooserHandler; import cz.cas.lib.proarc.webapp.client.widget.ImportBatchItemEditor; import cz.cas.lib.proarc.webapp.client.widget.ImportParentChooser; import cz.cas.lib.proarc.webapp.client.widget.ImportParentChooser.ImportParentHandler; import cz.cas.lib.proarc.webapp.client.widget.ImportSourceChooser; import cz.cas.lib.proarc.webapp.client.widget.ImportSourceChooser.ImportSourceChooserHandler; import cz.cas.lib.proarc.webapp.client.widget.ProgressTracker; import cz.cas.lib.proarc.webapp.client.widget.StatusView; import cz.cas.lib.proarc.webapp.client.widget.Wizard; import cz.cas.lib.proarc.webapp.client.widget.Wizard.StepKind; import cz.cas.lib.proarc.webapp.client.widget.Wizard.WizardStep; import java.util.logging.Logger; /** * * @author Jan Pokorsky */ public class ImportPresenter { private static final Logger LOG = Logger.getLogger(ImportPresenter.class.getName()); private final Wizard wizard; private final SelectFolderStep selectFolderStep; private final SelectBatchStep selectBatchStep; private final UpdateItemsStep updateItemsStep; private final SelectParentStep selectParentStep; private final FinishedStep finishedStep; private ImportContext importContext; private final ClientMessages i18n; private final PlaceController placeController; public ImportPresenter(ClientMessages i18n, PlaceController placeController) { this.i18n = i18n; selectFolderStep = new SelectFolderStep(); selectBatchStep = new SelectBatchStep(); selectParentStep = new SelectParentStep(); updateItemsStep = new UpdateItemsStep(); finishedStep = new FinishedStep(); wizard = new Wizard(i18n, selectFolderStep, selectBatchStep, updateItemsStep, selectParentStep, finishedStep); this.placeController = placeController; } public void bind() { importContext = new ImportContext(); } public void unbind() { } public Canvas getUI() { return wizard; } public ImportContext getImportContext() { return importContext; } public void importFolder() { wizard.moveAt(selectFolderStep); wizard.setShowButtons(false); } public void updateImportedObjects() { wizard.moveAt(updateItemsStep); wizard.setShowButtons(true); } public void updateImportedObjects(String batchId) { BatchRecord batchRecord = new BatchRecord(new Record()); batchRecord.setId(batchId); getImportContext().setBatch(batchRecord); wizard.moveAt(updateItemsStep); wizard.setShowButtons(false); } public void selectParent() { wizard.moveAt(selectParentStep); wizard.setShowButtons(false); } public void selectParent(String batchId) { BatchRecord batchRecord = new BatchRecord(new Record()); batchRecord.setId(batchId); getImportContext().setBatch(batchRecord); wizard.moveAt(selectParentStep); wizard.setShowButtons(false); } public void selectBatchFromHistory() { wizard.moveAt(selectBatchStep); wizard.setShowButtons(false); } public void finishImport() { wizard.moveAt(finishedStep); wizard.setShowButtons(true); } private void loadBatch(final String batchId, final Runnable callback) { Criteria criteria = new Criteria(ImportBatchDataSource.FIELD_ID, batchId); ImportBatchDataSource.getInstance().fetchData(criteria, new DSCallback() { @Override public void execute(DSResponse response, Object rawData, DSRequest request) { BatchRecord batchRecord = null; if (RestConfig.isStatusOk(response)) { Record[] records = response.getData(); if (records.length > 0) { batchRecord = new BatchRecord(records[0]); } else { SC.warn("Batch not found! " + batchId); } } getImportContext().setBatch(batchRecord); callback.run(); } }); } private void ingest(String batchId, String parentId, final BooleanCallback call) { ImportBatchDataSource dsBatch = ImportBatchDataSource.getInstance(); DSRequest dsRequest = new DSRequest(); dsRequest.setPromptStyle(PromptStyle.DIALOG); dsRequest.setPrompt(i18n.ImportWizard_UpdateItemsStep_Ingesting_Title()); Record update = new Record(); update.setAttribute(ImportBatchDataSource.FIELD_ID, batchId); update.setAttribute(ImportBatchDataSource.FIELD_PARENT, parentId); update.setAttribute(ImportBatchDataSource.FIELD_STATE, ImportBatchDataSource.State.INGESTING.name()); dsBatch.updateData(update, new DSCallback() { @Override public void execute(DSResponse response, Object rawData, DSRequest request) { if (RestConfig.isStatusOk(response)) { Record[] records = response.getData(); if (records != null && records.length > 0) { importContext.setBatch(new BatchRecord(records[0])); call.execute(true); return; } } call.execute(false); } }, dsRequest); } /** * Resets batch import. * Supports {@code LOADING_FAILED, LOADED, INGESTING_FAILED} for now. */ private void reset(BatchRecord batch, String newProfileId, final BooleanCallback call) { ImportBatchDataSource.State state = batch.getState(); String stateAttr; if (state == ImportBatchDataSource.State.LOADING_FAILED || state == ImportBatchDataSource.State.LOADED) { stateAttr = ImportBatchDataSource.State.LOADING_FAILED.name(); } else if (state == ImportBatchDataSource.State.INGESTING_FAILED) { stateAttr = ImportBatchDataSource.State.INGESTING.name(); } else { throw new IllegalStateException("Unsupported state: " + batch.getState()); } ImportBatchDataSource dsBatch = ImportBatchDataSource.getInstance(); DSRequest dsRequest = new DSRequest(); Record update = new Record(); update.setAttribute(ImportBatchDataSource.FIELD_ID, batch.getId()); update.setAttribute(ImportBatchDataSource.FIELD_STATE, stateAttr); if (newProfileId != null) { update.setAttribute(ImportBatchDataSource.FIELD_PROFILE_ID, newProfileId); } dsBatch.updateData(update, new DSCallback() { @Override public void execute(DSResponse response, Object rawData, DSRequest request) { if (RestConfig.isStatusOk(response)) { Record[] records = response.getData(); if (records != null && records.length > 0) { importContext.setBatch(new BatchRecord(records[0])); call.execute(true); return; } } call.execute(false); } }, dsRequest); } private void updateBatchParent(String batchId, final String parentPid, final BooleanCallback call) { ImportBatchDataSource dsBatch = ImportBatchDataSource.getInstance(); DSRequest dsRequest = new DSRequest(); Record update = new Record(); update.setAttribute(ImportBatchDataSource.FIELD_ID, batchId); update.setAttribute(ImportBatchDataSource.FIELD_PARENT, parentPid != null ? parentPid : ""); dsBatch.updateData(update, new DSCallback() { @Override public void execute(DSResponse response, Object rawData, DSRequest request) { if (!RestConfig.isStatusOk(response)) { importContext.setParentPid(null); call.execute(false); return; } Record[] data = response.getData(); if (data != null && data.length > 0) { importContext.setBatch(new BatchRecord(data[0])); call.execute(true); } else { // XXX show warning something is wrong importContext.setBatch(null); call.execute(false); } } }, dsRequest); } private final class SelectBatchStep implements WizardStep, ImportBatchChooserHandler { private ImportBatchChooser widget; private Wizard wizard; @Override public void onShow(Wizard wizard) { this.wizard = wizard; wizard.setWizardLabel(i18n.ImportWizard_DescriptionPrefix_Title(), i18n.ImportWizard_SelectBatchStep_Description_Title()); widget.setHandler(this); widget.bind(); } @Override public void onHide(Wizard wizard) { this.wizard = null; widget.setHandler(null); } @Override public boolean onStepAction(Wizard wizard, StepKind step) { return true; } @Override public Canvas asWidget() { if (widget == null) { widget = new ImportBatchChooser(i18n); } return widget; } @Override public void itemSelected() { BatchRecord batch = widget.getSelectedBatch(); getImportContext().setBatch(batch); placeController.goTo(new ImportPlace(Type.EDIT_ITEMS, batch.getId())); } @Override public void itemReset() { BatchRecord batch = widget.getSelectedBatch(); String profile = widget.getSelectedProfile(); reset(batch, profile, ClientUtils.EMPTY_BOOLEAN_CALLBACK); } } private final class SelectFolderStep implements WizardStep, ImportSourceChooserHandler { private ImportSourceChooser importSourceChooser; private Wizard wizard; @Override public void onShow(Wizard wizard) { this.wizard = wizard; wizard.setWizardLabel(i18n.ImportWizard_DescriptionPrefix_Title(), i18n.ImportWizard_SelectFolderStep_Description_Title()); ImportPresenter.this.importContext = new ImportContext(); importSourceChooser.setViewHandler(this); importSourceChooser.edit(); } @Override public void onHide(Wizard wizard) { importSourceChooser.setViewHandler(null); } @Override public boolean onStepAction(Wizard wizard, StepKind step) { return true; } @Override public Canvas asWidget() { if (importSourceChooser == null) { importSourceChooser = new ImportSourceChooser(i18n); } return importSourceChooser; } @Override public void sourceSelected() { handleImportSource(); } private void handleImportSource() { if (!importSourceChooser.validateOptions()) { return ; } Record record = importSourceChooser.getImportSource(); ImportRecord importRecord = record == null ? null : new ImportRecord(record); if (importRecord != null && importRecord.isNew()) { final ImportBatchDataSource dsBatch = ImportBatchDataSource.getInstance(); DSRequest dsRequest = new DSRequest(); dsRequest.setPromptStyle(PromptStyle.DIALOG); dsRequest.setPrompt(i18n.ImportWizard_SelectFolderStep_Wait_Title()); Record newBatch = dsBatch.newBatch(importRecord.getPath(), importSourceChooser.getImportProfile(), importSourceChooser.getDevice(), importSourceChooser.getGenerateIndices()); dsBatch.addData(newBatch, new DSCallback() { @Override public void execute(DSResponse response, Object rawData, DSRequest request) { if (!RestConfig.isStatusOk(response)) { response.setInvalidateCache(true); return; } Record[] data = response.getData(); if (data != null && data.length > 0) { BatchRecord newBatch = new BatchRecord(data[0]); if (newBatch.isArchive()) { SC.say(i18n.ImportWizard_SelectFolderStep_ImportScheduled_Title()); response.setInvalidateCache(true); dsBatch.updateCaches(response, request); } else { showProgress(newBatch); } } else { response.setInvalidateCache(true); SC.warn(i18n.ImportWizard_SelectFolderStep_NothingToImport_Msg()); } } }, dsRequest); } else { throw new IllegalStateException(); } } private void showProgress(final BatchRecord batch) { final Criteria criteria = new Criteria(ImportBatchItemDataSource.FIELD_BATCHID, batch.getId()); ImportBatchItemDataSource ds = ImportBatchItemDataSource.getInstance(); ProgressTracker progress = new ProgressTracker(i18n); progress.setCloseButton(i18n.ProgressTracker_Continue_Title(), i18n.ProgressTracker_Continue_Hint()); progress.setDataSource(ds, criteria); progress.setInit(); progress.setProgressPrefix(i18n.ImportWizard_SelectFolderStep_ImportProgress_Prefix_Title()); progress.showInWindow(new Runnable() { @Override public void run() { checkBatchState(batch); } }, i18n.ImportWizard_SelectFolderStep_ImportProgress_Title()); } private void checkBatchState(BatchRecord batch) { loadBatch(batch.getId(), new Runnable() { @Override public void run() { BatchRecord batch = getImportContext().getBatch(); if (batch != null && batch.getState() == ImportBatchDataSource.State.LOADED) { ImportPresenter.this.getImportContext().setBatch(batch); importSourceChooser.updateCache(ImportTreeDataSource.FolderState.IMPORTED); placeController.goTo(new ImportPlace(Type.EDIT_ITEMS, batch.getId())); } else if (batch.getState() == ImportBatchDataSource.State.LOADING) { // The import batch is still loading! An user chose to skip the progress. importSourceChooser.refreshSelectedNode(); } else { String error = batch.getLog(); if (error != null && !error.isEmpty()) { error = SafeHtmlUtils.htmlEscape(error); } else { error = "The import batch loading failed!"; } ErrorHandler.warn(error); importSourceChooser.refreshSelectedNode(); } } }); } } private final class UpdateItemsStep implements WizardStep, ImportBatchItemEditor.Handler { private ImportBatchItemEditor widget; @Override public void onShow(Wizard wizard) { wizard.setWizardLabel(i18n.ImportWizard_DescriptionPrefix_Title(), i18n.ImportWizard_UpdateItemsStep_Description_Title()); BatchRecord batch = getImportContext().getBatch(); widget.setHandler(this); widget.onShow(batch); } @Override public void onHide(Wizard wizard) { // XXX check } @Override public boolean onStepAction(final Wizard wizard, final StepKind step) { return true; } @Override public Canvas asWidget() { if (widget == null) { widget = new ImportBatchItemEditor(i18n); } return widget; } @Override public void handleNextAction() { BatchRecord batch = getImportContext().getBatch(); ImportPlace place = new ImportPlace(Type.EDIT_PARENT, batch.getId()); placeController.goTo(place); } } private final class SelectParentStep implements WizardStep, ImportParentHandler { private ImportParentChooser widget; private Wizard wizard; private VLayout container; private final ActionSource actionSource = new ActionSource(this); @Override public void onShow(Wizard wizard) { wizard.setWizardLabel(i18n.ImportWizard_DescriptionPrefix_Title(), i18n.ImportWizard_SelectParentStep_Description_Title()); this.wizard = wizard; BatchRecord batch = ImportPresenter.this.getImportContext().getBatch(); widget.getUI().hide(); loadBatch(batch.getId(), new Runnable() { @Override public void run() { bindData(); } }); } private void bindData() { BatchRecord batch = ImportPresenter.this.getImportContext().getBatch(); if (batch != null && batch.getId() != null) { widget.setHandler(this); widget.setImport(batch.getId()); } canStep(); } private void canStep() { BatchRecord batch = getImportContext().getBatch(); actionSource.fireEvent(); widget.getUI().setVisible(batch != null); } @Override public void onHide(Wizard wizard) { widget.setHandler(null); } @Override public boolean onStepAction(Wizard wizard, StepKind step) { final ImportContext importContext = ImportPresenter.this.getImportContext(); final String parentPid = importContext.getParentPid(); String batchId = importContext.getBatch().getId(); if (step == StepKind.FORWARD) { if (parentPid != null) { fetchItems(batchId, parentPid); } return false; } else { placeController.goTo(new ImportPlace(Type.EDIT_ITEMS, batchId)); return false; } } private void fetchItems(final String batchId, final String parentPid) { final Criteria criteria = new Criteria(ImportBatchItemDataSource.FIELD_BATCHID, batchId); ImportBatchItemDataSource ds = ImportBatchItemDataSource.getInstance(); ds.fetchData(criteria, new DSCallback() { @Override public void execute(DSResponse response, Object rawData, DSRequest request) { if (RestConfig.isStatusOk(response)) { validateItems(batchId, parentPid, response.getData()); } } }); } private void validateItems(final String batchId, final String parentPid, final Record[] items) { final DigitalObjectFormValidateAction action = DigitalObjectFormValidateAction.getInstance(i18n); action.validate(new Validatable() { private boolean valid; @Override public void clearErrors(Record r) { // no-op } @Override public void init() { valid = true; } @Override public void setErrors(Record r, String errors) { valid = false; } @Override public Record[] getSelection() { return items; } @Override public void onFinish(boolean canceled) { if (!canceled) { if (valid) { action.closeWindow(); ingest(batchId, parentPid); } } } }); } private void ingest(String batchId, String parentPid) { ImportPresenter.this.ingest(batchId, parentPid, new BooleanCallback() { @Override public void execute(Boolean value) { BooleanCallback bc = new BooleanCallback() { @Override public void execute(Boolean value) { placeController.goTo(new DigitalObjectManagerPlace()); } }; // XXX implement status page listing items and their states if (value != null && value) { SC.say(i18n.ImportWizard_Ingest_Done_Msg(), bc); } else { SC.warn(i18n.ImportWizard_Ingest_Failed_Msg(), bc); } } }); } private void updateBatchParent(String batchId, final String parentPid) { ImportPresenter.this.updateBatchParent(batchId, parentPid, new BooleanCallback() { @Override public void execute(Boolean value) { StatusView.getInstance().show(i18n.ImportParentChooser_SaveAction_Done_Msg()); canStep(); } }); } @Override public Canvas asWidget() { if (widget == null) { widget = new ImportParentChooser(i18n); widget.setParentOwnerCheck(true); widget.getUI().setMargin(4); container = new VLayout(); container.setMembers(buildToolbar(), widget.getUI()); } return container; } private Canvas buildToolbar() { Action ingestAction = new AbstractAction(i18n.ImportWizard_ButtonImport_Title(), "[SKIN]/actions/save.png", i18n.ImportWizard_ButtonImport_Hint()) { @Override public void performAction(ActionEvent event) { onStepAction(wizard, StepKind.FORWARD); } @Override public boolean accept(ActionEvent event) { BatchRecord batch = getImportContext().getBatch(); String batchId = batch != null ? batch.getId() : null; String parentPid = batch != null ? batch.getParentPid() : null; return batchId != null && parentPid != null; } }; Action backAction = new AbstractAction(i18n.ImportWizard_UpdateItemsStepAction_Title(), "[SKIN]/actions/prev.png", i18n.ImportWizard_UpdateItemsStepAction_Hint()) { @Override public void performAction(ActionEvent event) { onStepAction(wizard, StepKind.BACK); } }; ToolStrip toolbar = Actions.createToolStrip(); toolbar.addMember(Actions.asIconButton(new RefreshAction(i18n), this)); toolbar.addMember(Actions.asIconButton(backAction, this)); toolbar.addMember(Actions.asIconButton(ingestAction, actionSource)); return toolbar; } @Override public void onParentSelectionUpdated() { Record selectedParent = widget.getSelectedParent(); String pid = (selectedParent != null) ? selectedParent.getAttribute(RelationDataSource.FIELD_PID) : null; String batchId = getImportContext().getBatch().getId(); updateBatchParent(batchId, pid); } } /** * This should summerize imported objects and error logs. */ private final class FinishedStep implements WizardStep { private final Canvas widget; public FinishedStep() { widget = new Canvas(); widget.setContents("Imported!"); } @Override public void onShow(Wizard wizard) { wizard.setBackButton(true, "Import New Folder"); wizard.setForwardButton(false, null); wizard.setWizardLabel(i18n.ImportWizard_DescriptionPrefix_Title(), "done"); } @Override public void onHide(Wizard wizard) { } @Override public boolean onStepAction(Wizard wizard, StepKind step) { ImportPresenter.this.importFolder(); return false; } @Override public Canvas asWidget() { return widget; } } public static final class ImportContext { private BatchRecord batch; public BatchRecord getBatch() { return batch; } public void setBatch(BatchRecord batch) { this.batch = batch; } public String getParentPid() { return batch != null ? batch.getParentPid() : null; } public void setParentPid(String pid) { batch.setParentPid(pid); } } }