/* * 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.action; import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.smartgwt.client.data.Record; import com.smartgwt.client.data.ResultSet; import com.smartgwt.client.util.BooleanCallback; import com.smartgwt.client.widgets.grid.ListGrid; import com.smartgwt.client.widgets.grid.ListGridRecord; import cz.cas.lib.proarc.webapp.client.ClientMessages; import cz.cas.lib.proarc.webapp.client.ds.DigitalObjectDataSource.DigitalObject; import cz.cas.lib.proarc.webapp.client.ds.MetaModelDataSource; import cz.cas.lib.proarc.webapp.client.presenter.ModsCustomEditor; import cz.cas.lib.proarc.webapp.client.widget.ProgressTracker; /** * Validates list of digital objects gained from * a {@link Validatable} action source or passed to the constructor. * * <p>For now it runs fully on the client side. It uses MODS custom forms * for validation. The result of validation is shown inside a window and error * messages are added to each invalid row of the list. It is the list owner * responsibility to clear errors (e.g. on refresh). * * <p>The action shows processing status with the {@link ProgressTracker}. * * <p>The action is a singleton to share form instances. * * @author Jan Pokorsky */ public class DigitalObjectFormValidateAction extends AbstractAction { private static DigitalObjectFormValidateAction INSTANCE; private final ModsCustomEditor validator; private Validatable validatable; private final ClientMessages i18n; private final ProgressTracker progress; public static DigitalObjectFormValidateAction getInstance(ClientMessages i18n) { if (INSTANCE == null) { INSTANCE = new DigitalObjectFormValidateAction(i18n); } return INSTANCE; } private DigitalObjectFormValidateAction(ClientMessages i18n) { this(i18n, null); } public DigitalObjectFormValidateAction(ClientMessages i18n, Validatable validatable) { super(i18n.DigitalObjectFormValidateAction_Title(), "[SKIN]/actions/configure.png", i18n.DigitalObjectFormValidateAction_Hint()); this.i18n = i18n; this.validator = new ModsCustomEditor(i18n); this.progress = new ProgressTracker(i18n); this.validatable = validatable; } @Override public void performAction(ActionEvent event) { validate(getValidable(event)); } public void validate(final Validatable validable) { if (validable != null) { Record[] selection = validable.getSelection(); if (selection != null && selection.length > 0) { validate(validable, selection); } } } public void closeWindow() { progress.stop(); } private void validate(final Validatable validable, final Record[] digitalObjects) { if (digitalObjects != null && digitalObjects.length > 0) { // ensure models are fetched MetaModelDataSource.getModels(false, new Callback<ResultSet, Void>() { @Override public void onFailure(Void reason) { } @Override public void onSuccess(ResultSet result) { new ValidateTask(validable, digitalObjects).execute(); } }); } else { // no-op } } private Validatable getValidable(ActionEvent event) { Object source = event.getSource(); if (source instanceof Validatable) { return (Validatable) source; } return validatable; } private final class ValidateTask implements ScheduledCommand { private int index = 0; private int length = -1; private int invalidItemsCount; private final Record[] digitalObjects; private boolean stop = false; private final Validatable validatable; public ValidateTask(Validatable validatable, Record[] digitalObjects) { this.digitalObjects = digitalObjects; this.validatable = validatable; } @Override public void execute() { if (length < 0) { initTask(); } if (!stop && index < length) { validateMods(); } else { closeTask(); } } private void initTask() { validatable.init(); length = digitalObjects.length; invalidItemsCount = 0; progress.setInit(); progress.showInWindow(new Runnable() { @Override public void run() { stop = true; } }, i18n.DigitalObjectFormValidateAction_ProgressWindow_Title()); } private void closeTask() { if (invalidItemsCount == 0) { progress.setDone(i18n.DigitalObjectFormValidateAction_NoError_Msg( String.valueOf(length))); } else { progress.setDone( i18n.DigitalObjectFormValidateAction_Errors_Msg( String.valueOf(invalidItemsCount), String.valueOf(length) ), true); } validatable.onFinish(index != length); } private void validateMods() { progress.setProgress(index, length); final Record record = digitalObjects[index]; DigitalObject dobj = DigitalObject.create(record); validator.setShowFetchPrompt(false); validator.edit(dobj, new BooleanCallback() { @Override public void execute(Boolean value) { if (value != null && value) { consumeValidation(record, validator.isValidDigitalObject()); } else { // unknown validity } ++index; // Scheduler is not necessary as fetch operations // run asynchronously ValidateTask.this.execute(); } }); } private void consumeValidation(Record r, boolean valid) { if (valid) { validatable.clearErrors(r); } else { ++invalidItemsCount; validatable.setErrors(r, i18n.DigitalObjectFormValidateAction_ListRowError_Hint()); } } } /** * Describes selected digital objects that should be validated. * <p>Use it as an action source. */ public interface Validatable extends Selectable<Record> { void clearErrors(Record r); /** * Called before validation start. */ void init(); void onFinish(boolean canceled); void setErrors(Record r, String errors); } /** * Implements {@link Validatable} for the {@link ListGrid}. * <p>If the ListGrid does not return a multi selection then all records * are used. * <p>The list should be fully fetched before validation! */ public static class ValidatableList implements Validatable { private final ListGrid list; private String fieldName; /** * Clears all row errors. * @param lg list to clear */ public static void clearRowErrors(ListGrid lg) { for (int i = lg.getRecords().length - 1; i >= 0; i--) { lg.clearRowErrors(i); } } public ValidatableList(ListGrid list) { if (list == null) { throw new NullPointerException("list"); } this.list = list; } @Override public void clearErrors(Record r) { int rowNum = list.getRecordIndex(r); list.clearRowErrors(rowNum); } @Override public void init() { // use the first visible column to set errors fieldName = list.getFieldName(0); // force grid to show errors list.setCanEdit(true); } @Override public void setErrors(Record r, String errors) { int rowNum = list.getRecordIndex(r); list.setFieldError(rowNum, fieldName, errors); } @Override public Record[] getSelection() { ListGridRecord[] records = list.getSelectedRecords(); if (records == null || records.length == 0 || records.length == 1) { // if it is not a multi selection then use all records of the list ResultSet resultSet = list.getResultSet(); if (resultSet.allMatchingRowsCached()) { records = list.getRecords(); } } return records; } @Override public void onFinish(boolean canceled) { list.setCanEdit(false); } } }