/* * Copyright (C) 2013 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.core.client.Callback; import com.smartgwt.client.data.Record; import com.smartgwt.client.types.Overflow; import com.smartgwt.client.util.BooleanCallback; import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.Canvas; import com.smartgwt.client.widgets.form.events.SubmitValuesEvent; import com.smartgwt.client.widgets.form.events.SubmitValuesHandler; import cz.cas.lib.proarc.common.i18n.BundleName; import cz.cas.lib.proarc.webapp.client.ClientMessages; import cz.cas.lib.proarc.webapp.client.ClientUtils; import cz.cas.lib.proarc.webapp.client.action.DigitalObjectCopyMetadataAction; import cz.cas.lib.proarc.webapp.client.action.RefreshAction.Refreshable; import cz.cas.lib.proarc.webapp.client.ds.DigitalObjectDataSource.DigitalObject; import cz.cas.lib.proarc.webapp.client.ds.ModsCustomDataSource; import cz.cas.lib.proarc.webapp.client.ds.ModsCustomDataSource.DescriptionMetadata; import cz.cas.lib.proarc.webapp.client.ds.ModsCustomDataSource.DescriptionSaveHandler; import cz.cas.lib.proarc.webapp.client.widget.AbstractDatastreamEditor; import cz.cas.lib.proarc.webapp.client.widget.BatchDatastreamEditor; import cz.cas.lib.proarc.webapp.client.widget.CopyPageMetadataWidget; import cz.cas.lib.proarc.webapp.client.widget.PageMetadataEditor; import cz.cas.lib.proarc.webapp.client.widget.ProgressTracker; import java.util.Iterator; /** * Support for batch edits of MODS of digital objects. * * <p>For now accepts selection of pages. * * @author Jan Pokorsky */ public final class ModsBatchEditor extends AbstractDatastreamEditor implements BatchDatastreamEditor, Refreshable { private BatchJob editor; private DigitalObject[] digitalObjects; private final ProgressTracker progress; private final ClientMessages i18n; private final BatchJob metadataGenerator; private final BatchJob metadataDuplicator; public ModsBatchEditor(ClientMessages i18n) { this.i18n = i18n; this.metadataGenerator = new GenerateJob(this); this.metadataDuplicator = new CopyJob(this); this.progress = new ProgressTracker(i18n); switchEditor(); } @Override public void edit(DigitalObject[] items) { this.digitalObjects = items; switchEditor(); editor.selectionChanged(); } private void switchEditor() { Record[] selection = DigitalObjectCopyMetadataAction.getSelection(); if (selection != null) { editor = metadataDuplicator; } else { editor = metadataGenerator; } } @Override public void edit(DigitalObject digitalObject) { throw new UnsupportedOperationException(); } @Override public void focus() { editor.focus(); } @Override @SuppressWarnings("unchecked") public <T> T getCapability(Class<T> clazz) { T c = null; if (Refreshable.class.equals(clazz) || BatchDatastreamEditor.class.equals(clazz)) { c = (T) this; } return c; } @Override public Canvas[] getToolbarItems() { return new Canvas[0]; } @Override public Canvas getUI() { switchEditor(); return editor.getFormPanel(); } @Override public void refresh() { editor.refreshPanel(); } void save(BooleanCallback callback) { if (editor.validatePanel()) { editor.execute(callback); } } private static abstract class BatchJob { private int index = 0; private int length = -1; private boolean stop = false; private String errorMsg; private final ModsBatchEditor editor; private BooleanCallback taskDoneCallback; protected final ClientMessages i18n; public BatchJob(ModsBatchEditor editor) { this.editor = editor; this.i18n = editor.i18n; } public void execute(BooleanCallback taskDoneCallback) { this.taskDoneCallback = taskDoneCallback; index = 0; length = -1; stop = false; execute(); } protected void execute() { if (length < 0) { init(); } if (!stop && index < length) { processStep(); } else { close(); } } public ProgressTracker getProgress() { return editor.progress; } public int getCurrentIndex() { return index; } public DigitalObject getCurrent() { return editor.digitalObjects[getCurrentIndex()]; } public DigitalObject[] getSelection() { return editor.digitalObjects; } public void next() { ++index; getProgress().setProgress(index, length); execute(); } public void stop(String reason) { stop = true; errorMsg = reason; execute(); } public void focus() { getFormPanel().focus(); } public abstract Canvas getFormPanel(); public abstract void refreshPanel(); public abstract boolean validatePanel(); protected abstract void processStep(); protected void init() { length = getSelection().length; getProgress().setInit(); getProgress().showInWindow(new Runnable() { @Override public void run() { stop = true; } }); } /** * Implement to modify UI according to selection changes. */ protected void selectionChanged() { } private void close() { if (errorMsg != null) { getProgress().stop(); SC.warn(errorMsg); } else { getProgress().stop(); } if (taskDoneCallback != null) { taskDoneCallback.execute(errorMsg == null); } } void saveMods(DescriptionMetadata description) { ModsCustomDataSource.getInstance().saveDescription( description, new DescriptionSaveHandler() { @Override protected void onSave(DescriptionMetadata dm) { super.onSave(dm); next(); } @Override protected void onConcurrencyError() { stop("Update failed!"); } @Override protected void onError() { super.onError(); stop("Update failed!"); } @Override protected void onValidationError() { stop(getValidationMessage()); } }, false); } } /** * Generates metadata for selected digital objects. */ private static class GenerateJob extends BatchJob { private final PageMetadataEditor editor; /** An increment of the job item index used to process only required pages. */ private int batchApplyTo; /** If {@code 0} then values are applied from the first item, otherwise use {@code 1}. */ private int batchApplyFromFirstItem; private Integer batchIndexStart; private Iterator<String> batchSequence; private String batchNumberFormat; private Canvas panel; public GenerateJob(final ModsBatchEditor editor) { super(editor); this.editor = new PageMetadataEditor(); this.editor.setSubmitHandler(new SubmitValuesHandler() { @Override public void onSubmitValues(SubmitValuesEvent event) { editor.fireEvent(event); } }); } @Override public Canvas getFormPanel() { if (panel == null) { panel = editor.getFormPanel(); panel.setWidth100(); panel.setHeight100(); panel.setOverflow(Overflow.AUTO); } return panel; } @Override public void refreshPanel() { editor.initAll(); } @Override public boolean validatePanel() { editor.setMaxApplyTo(getSelection().length); return editor.validate(); } @Override protected void init() { super.init(); batchIndexStart = null; batchSequence = null; batchNumberFormat = "%s"; if (editor.getAllowPageIndexes()) { batchIndexStart = editor.getIndexStart(); } if (editor.getAllowPageNumbers()) { batchSequence = editor.getSequence(); String prefix = editor.getPrefix(); String suffix = editor.getSuffix(); if (prefix != null) { batchNumberFormat = prefix + batchNumberFormat; } if (suffix != null) { batchNumberFormat += suffix; } } batchApplyTo = editor.getApplyTo(); batchApplyFromFirstItem = editor.getApplyFromFirstItem() ? 0 : 1; if (batchApplyTo > getSelection().length) { stop(i18n.PageMetadataEditor_ApplyToErrOutOfBounds_Msg(String.valueOf(batchApplyTo))); } } @Override protected void selectionChanged() { super.selectionChanged(); DigitalObject[] selection = getSelection(); if (selection != null && selection.length > 0) { String modelId = selection[0].getModelId(); String pageTypeMapId = null; if ("model:page".equals(modelId)) { pageTypeMapId = BundleName.MODS_PAGE_TYPES.getValueMapId(); } else if ("model:oldprintpage".equals(modelId)) { pageTypeMapId = BundleName.MODS_OLDPRINT_PAGE_TYPES.getValueMapId(); } if (pageTypeMapId != null) { editor.setPageTypeValueMapId(pageTypeMapId); } } } @Override protected void processStep() { if ((getCurrentIndex() + batchApplyFromFirstItem) % batchApplyTo == 0) { fetchMods(getCurrent()); } else { next(); } } private void fetchMods(final DigitalObject dobj) { ModsCustomDataSource.getInstance().fetchDescription(dobj, new Callback<DescriptionMetadata, String>() { @Override public void onFailure(String reason) { stop(reason); } @Override public void onSuccess(DescriptionMetadata result) { updatePage(result); } }, false); } private void updatePage(DescriptionMetadata description) { Record customModsRecord = description.getDescription(); // fill data // RPCManager.startQueue(); if (editor.getAllowPageIndexes()) { String old = customModsRecord.getAttributeAsString(ModsCustomDataSource.FIELD_PAGE_INDEX); String newVal = batchIndexStart == null ? null : String.valueOf(batchIndexStart++); newVal = (old != null && newVal == null) ? "" : newVal; customModsRecord.setAttribute(ModsCustomDataSource.FIELD_PAGE_INDEX, newVal); } if (editor.getAllowPageNumbers()) { String old = customModsRecord.getAttributeAsString(ModsCustomDataSource.FIELD_PAGE_NUMBER); String newVal = batchSequence != null ? ClientUtils.format(batchNumberFormat, batchSequence.next()) : ClientUtils.format(batchNumberFormat, ""); newVal = newVal.isEmpty() ? null : newVal; newVal = (old != null && newVal == null) ? "" : newVal; customModsRecord.setAttribute(ModsCustomDataSource.FIELD_PAGE_NUMBER, newVal); } if (editor.getAllowPageTypes()) { String pageType = editor.getPageType(); customModsRecord.setAttribute(ModsCustomDataSource.FIELD_PAGE_TYPE, pageType); } ClientUtils.removeNulls(customModsRecord); // RPCManager.sendQueue(); saveMods(description); } } /** * Copies metadata to selected digital objects. * It takes {@link DigitalObjectCopyMetadataAction#getSelection() } * as a source. */ private static class CopyJob extends BatchJob { private Record[] templateRecords; private DescriptionMetadata[] templateDescriptions; private Canvas panel; private final CopyPageMetadataWidget widget; public CopyJob(ModsBatchEditor editor) { super(editor); widget = new CopyPageMetadataWidget(); widget.initAll(); } @Override public Canvas getFormPanel() { if (panel == null) { panel = widget.getPanel(); panel.setWidth100(); panel.setHeight100(); panel.setOverflow(Overflow.AUTO); } return panel; } @Override protected void init() { templateRecords = DigitalObjectCopyMetadataAction.getSelection(); templateDescriptions = new DescriptionMetadata[templateRecords.length]; super.init(); } @Override public void refreshPanel() { widget.initAll(); } @Override public boolean validatePanel() { return widget.validate(); } @Override protected void processStep() { updateDescription(); } private void updateDescription() { int templateIndex = getTemplateIndex(); Record templateRecord = templateRecords[templateIndex]; prepareTemplate(DigitalObject.create(templateRecord), templateDescriptions[templateIndex]); } private void updatePage(DescriptionMetadata descCurrent, DescriptionMetadata descTemplate) { Record current = descCurrent.getDescription(); Record template = descTemplate.getDescription(); copyAttribute(widget.getAllowPageIndexes(), template, current, ModsCustomDataSource.FIELD_PAGE_INDEX); copyAttribute(widget.getAllowPageNumbers(), template, current, ModsCustomDataSource.FIELD_PAGE_NUMBER); copyAttribute(widget.getAllowPageTypes(), template, current, ModsCustomDataSource.FIELD_PAGE_TYPE); saveMods(descCurrent); } private void prepareDescription(DigitalObject dobj, final DescriptionMetadata templateDesc) { ModsCustomDataSource.getInstance().fetchDescription(dobj, new Callback<DescriptionMetadata, String>() { @Override public void onFailure(String reason) { stop(reason); } @Override public void onSuccess(DescriptionMetadata result) { updatePage(result, templateDesc); } }, false); } private void prepareTemplate(DigitalObject templateObj, DescriptionMetadata templateDesc) { if (templateDesc != null) { prepareDescription(getCurrent(), templateDesc); return ; } ModsCustomDataSource.getInstance().fetchDescription(templateObj, new Callback<DescriptionMetadata, String>() { @Override public void onFailure(String reason) { stop(reason); } @Override public void onSuccess(DescriptionMetadata result) { templateDescriptions[getTemplateIndex()] = result; prepareDescription(getCurrent(), result); } }, false); } private void copyAttribute(boolean enabled, Record src, Record dst, String attrName) { if (enabled) { String value = src.getAttribute(attrName); if (value != null) { dst.setAttribute(attrName, value); } } } private int getTemplateIndex() { int tmplIndex = getCurrentIndex() % templateRecords.length; return tmplIndex; } } }