com.fluidops.iwb.ajax.FMultiStageInputWizard.java Source code

Java tutorial

Introduction

Here is the source code for com.fluidops.iwb.ajax.FMultiStageInputWizard.java

Source

/*
 * Copyright (C) 2008-2013, fluid Operations AG
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
    
 * This library 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
 * Lesser General Public License for more details.
    
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.fluidops.iwb.ajax;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.Repository;

import com.fluidops.ajax.FClientUpdate;
import com.fluidops.ajax.components.FButton;
import com.fluidops.ajax.components.FContainer;
import com.fluidops.ajax.components.FHTML;
import com.fluidops.ajax.components.FLabel;
import com.fluidops.ajax.components.FPopupWindow;
import com.fluidops.ajax.wizard.FStep;
import com.fluidops.ajax.wizard.FWizard;
import com.fluidops.iwb.ajax.FStatementInput.InvalidUserInputException;
import com.fluidops.iwb.ajax.FStatementInputGroup.GroupLayout;
import com.fluidops.iwb.api.Context.ContextLabel;
import com.fluidops.iwb.api.EndpointImpl;
import com.fluidops.iwb.api.ReadDataManagerImpl;
import com.fluidops.iwb.model.Vocabulary;
import com.fluidops.iwb.service.CodeExecution;
import com.fluidops.iwb.widget.CodeExecutionWidget;
import com.fluidops.iwb.widget.CodeExecutionWidget.WidgetCodeConfig;
import com.fluidops.iwb.widget.DataInputWidget;
import com.fluidops.iwb.widget.DataInputWidget.AfterFinishAction;
import com.fluidops.iwb.widget.DataInputWidget.StatementInput;
import com.fluidops.iwb.widget.WidgetEmbeddingError;
import com.fluidops.iwb.widget.WidgetEmbeddingError.ErrorType;
import com.fluidops.util.Pair;
import com.fluidops.util.Rand;
import com.fluidops.util.StringUtil;

/**
 * A customizable wizard, consisting of a set of lazily initialized steps for
 * statement input.
 * 
 * @author msc
 */
public class FMultiStageInputWizard extends FWizard {
    private static final Logger logger = Logger.getLogger(FMultiStageInputWizard.class.getName());

    /**
     * The base URI (used for subjects), may be generated by a first wizard step
     * or may be fixed from the beginning
     */
    private Resource subject;

    /**
     * Redirect to subject when finished?
     */
    private boolean redirectToSubject;

    /**
     * The wizard description string
     */
    private String wizardDescription;

    /**
     * The (list of) type/s for the resource covered by the wizard.
     */
    private List<URI> types;

    /**
     * The repository used for extracting and saving data.
     */
    private Repository rep;

    /**
     * Flag for storing the creation date
     */
    private boolean saveCreationDate;

    /**
     * Flag for storing the creator
     */
    private boolean saveCreator;

    /**
     * Flag for storing the last modification date
     */
    private boolean saveLastModificationDate;

    /**
     * Field for storing message that occurred when saving the data
     */
    public String saveProblems = null;

    /**
     * see {@link CodeExecutionWidget} for examples
     */
    public WidgetCodeConfig onSaveMethod;

    /**
     * The surrounding DataInputWidget
     */
    public DataInputWidget parent;

    /**
     * Reload or redirect to new page on cancel or on submit
     */
    public AfterFinishAction doAfterFinish;

    /**
     * Constructor.
     * 
     * @param id
     *            String The FComponent id of the wizard
     * @param subject
     *            Resoucre The subject (to be specified if there is no
     *            ID-generating step)
     * @param types
     *            List<URI> The types of the wizard's subject resource
     * @param rep
     *            Repository The repository where the wizard stores data (and
     *            reads labels etc.)
     * @param redirectToSubject
     *            boolean If set to true, the wizard redirects to the subject
     *            page after finished
     * @param saveCreationDate
     *            boolean If set to true, the creation date is stored
     * @param onSaveMethod
     *           Method to invoke when the input wizard is saved (may be null)
     */
    public FMultiStageInputWizard(String id, Resource subject, List<URI> types, Repository rep,
            boolean redirectToSubject, boolean showDescriptionPage, boolean saveCreationDate, boolean saveCreator,
            boolean saveLastModificationDate, WidgetCodeConfig onSaveMethod, AfterFinishAction doAfterFinish,
            DataInputWidget parent) {
        super(id, showDescriptionPage);
        this.subject = subject;
        this.types = types;
        this.rep = rep;
        this.redirectToSubject = redirectToSubject;
        this.saveCreationDate = saveCreationDate;
        this.saveCreator = saveCreator;
        this.saveLastModificationDate = saveLastModificationDate;
        this.onSaveMethod = onSaveMethod;
        this.doAfterFinish = doAfterFinish;
        this.parent = parent;

        if (id == null || (subject == null && (types == null || types.isEmpty())) || rep == null)
            throw new RuntimeException("Invalid wizard initialization");
    }

    @Override
    public String getDescriptionString() {
        return wizardDescription;
    }

    @Override
    public String getTitle() {
        return "Wizard";
    }

    @Override
    public String render() {
        // fix floating bug 6194
        // fix fix (bug 6302): clear:both kills IE7, moved to CSS for IE7-specific handling
        return super.render() + "<div class=\"WizardClearBoth\" />";
    }

    @Override
    public FStep returnFirstStep() {
        // return user defined step
        List<FStep> steps = getSteps();
        int stepSize = steps.size();

        // if there is no description in place
        // the first step is the user's step
        if (!isShowDescriptionPage()) {
            if (stepSize == 0)
                return null;
            return steps.get(0);
        }
        // if there is an description in place
        // the first step is the step after the description
        else {
            if (stepSize < 2)
                return null;
            return steps.get(1);
        }
    }

    @Override
    public void doAfterFinish() {
        // execute method, if necessary
        if (onSaveMethod != null) {
            try {
                // TODO maybe add feature to give the CodeExecutionContext
                // problem is: how to obtain PageContext in this component
                onSaveMethod.passContext = onSaveMethod.passContext == null ? Boolean.FALSE
                        : onSaveMethod.passContext;
                CodeExecution.execute(onSaveMethod, null);
            } catch (Exception e) {
                logger.warn("Error while executing code: ", e);
                throw new RuntimeException("Error: " + e.getMessage());
            }
        }

        // redirect to new page
        final String redirect = EndpointImpl.api().getRequestMapper().getRequestStringFromValue(subject);

        if (StringUtil.isNullOrEmpty(saveProblems)) {
            if (doAfterFinish == AfterFinishAction.REDIRECT_TO_SUBJECT)
                addClientUpdate(new FClientUpdate("document.location='" + redirect + "'"));
            else if (doAfterFinish == AfterFinishAction.RELOAD)
                addClientUpdate(new FClientUpdate("document.location=document.location"));
            // else if (doAfterFinish==AfterFinishAction.NONE) -> nothing to do
        } else {
            final FPopupWindow p = getPage().getPopupWindowInstance();
            p.removeAll();
            p.setTitle("Problems while saving the changes");
            FContainer c = new FContainer("c" + Rand.getIncrementalFluidUUID());
            FHTML l = new FHTML("l" + Rand.getIncrementalFluidUUID());
            l.setValue(StringEscapeUtils.escapeHtml(saveProblems));
            FButton confirm = new FButton("b" + Rand.getIncrementalFluidUUID()) {
                @Override
                public void onClick() {
                    p.hide();
                    p.populateView();
                    if (redirectToSubject)
                        addClientUpdate(new FClientUpdate("document.location='" + redirect + "'"));
                    else
                        addClientUpdate(new FClientUpdate("document.location=document.location"));
                }
            };
            confirm.setValue(" OK ");
            c.add(l);
            c.add(confirm);
            p.add(c);
            p.populateView();
            p.show();
        }
    }

    @Override
    public void onCancel() {
        addClientUpdate(new FClientUpdate("window.location.href=window.location.href"));
        parent.onWizardCancel();
    }

    @Override
    protected void openReferer() {
        // do nothing here, bug 12346
    }

    @Override
    public void finish() throws Exception {
        FStep lastStep = allRegisteredSteps.get(allRegisteredSteps.size() - 1);
        if (lastStep instanceof FStatementInputGroupStep) {
            FStatementInputGroupStep lastStepCst = (FStatementInputGroupStep) lastStep;
            if (lastStepCst.getGroup() instanceof FIdStatementInputGroup)
                lastStepCst.onNext();

        }

        List<Statement> addStmts = new ArrayList<Statement>();
        List<Statement> removeStmts = new ArrayList<Statement>();
        List<Pair<Statement, Statement>> changeList = new ArrayList<Pair<Statement, Statement>>();

        for (int i = 0; i < getSteps().size(); i++) {
            FStep stepUncasted = getSteps().get(i);
            if (stepUncasted instanceof FStatementInputGroupStep) {
                FStatementInputGroupStep step = (FStatementInputGroupStep) stepUncasted;

                // FIdStatementInputGroup is responsible for ID generation,
                // so it actually contributes the baseURI (which should have
                // been set meanwhile)
                if (step.getGroup() instanceof FIdStatementInputGroup) {
                    if (((FIdStatementInputGroup) step.getGroup()).saveType()) {
                        for (int j = 0; j < types.size(); j++) {
                            Statement typeStmt = ValueFactoryImpl.getInstance().createStatement(subject, RDF.TYPE,
                                    types.get(j));
                            addStmts.add(typeStmt);
                        }
                    }

                    if (saveCreationDate) {
                        Statement saveCreationDateStmt = ValueFactoryImpl.getInstance().createStatement(subject,
                                Vocabulary.DC.DATE, ValueFactoryImpl.getInstance()
                                        .createLiteral(ReadDataManagerImpl.dateToISOliteral(new Date())));
                        addStmts.add(saveCreationDateStmt);
                    }

                    if (saveCreator) {
                        URI user = EndpointImpl.api().getUserURI();
                        Statement saveCreatorStmt = ValueFactoryImpl.getInstance().createStatement(subject,
                                Vocabulary.DC.CREATOR, user);
                        addStmts.add(saveCreatorStmt);
                    }
                }

                if (saveLastModificationDate) {
                    List<Statement> oldModificationDateStmts = EndpointImpl.api().getDataManager()
                            .getStatementsAsList(subject, Vocabulary.DCTERMS.MODIFIED, null, false);
                    Statement saveModificationDateStmt = ValueFactoryImpl.getInstance().createStatement(subject,
                            Vocabulary.DCTERMS.MODIFIED, ValueFactoryImpl.getInstance()
                                    .createLiteral(ReadDataManagerImpl.dateToISOliteral(new Date())));
                    removeStmts.addAll(oldModificationDateStmts);
                    addStmts.add(saveModificationDateStmt);
                }

                // collect triples generated by the individual section
                try {
                    step.collectStatements(addStmts, removeStmts, changeList);
                } catch (InvalidUserInputException e) {
                    saveProblems = e.getMessage();
                    return;
                }

            } // else: ignore description step, if available
        }

        try {
            StatementInputHelper.saveStatementInputs(rep, addStmts, removeStmts, changeList,
                    ContextLabel.DATA_INPUT_FORM);
        } catch (Exception e) {
            saveProblems = e.getMessage();
        }

        parent.onWizardFinish();
    }

    public void setBaseURI(URI uri) {
        subject = uri;
    }

    public void setDescription(String description) {
        this.wizardDescription = description;
    }

    /**
     * Single wizard step, wrapping around FStatementInputGroup pages
     * 
     * @author msc
     */
    public class FStatementInputGroupStep extends FStep {
        /** the inner page **/
        private FStatementInputGroup group;

        private FStep next;

        private FStep prev;

        public FStatementInputGroupStep(String id, String name, FWizard wizard, FStatementInputGroup group) {
            super(id, name, wizard);
            this.group = group;
        }

        @Override
        public void init() {
            if (!(group instanceof FIdStatementInputGroup))
                updateInitSubjects(subject);

            if (!group.isInitialized())
                group.prepareInitializeView();

            add(group);
        }

        @Override
        public FStep previousStep() {
            return prev;
        }

        @Override
        public FStep nextStep() {
            return next;
        }

        // set next step belatedly
        public void setNextStep(FStep step) {
            this.next = step;
        }

        // set previous step belatedly
        public void setPreviousStep(FStep step) {
            this.prev = step;
        }

        public void updateInitSubjects(Resource subject) {
            group.updateInitSubjects(subject);
        }

        public void collectStatements(List<Statement> addStmts, List<Statement> removeStmts,
                List<Pair<Statement, Statement>> changeList) throws InvalidUserInputException {
            if (group != null)
                group.collectStatements(addStmts, removeStmts, changeList);
        }

        public FStatementInputGroup getGroup() {
            return group;
        }
    }

    /**
     * Appends a statement input group step to the wizard according to the given
     * configuration (see class CustomizableNewInstanceWidget for a description
     * of the config).
     * TODO CustomizableNewInstanceWidget does not exist
     * 
     * @return null if everything goes fine, an error label otherwise.
     */
    public FLabel appendIdStatementInputStep(String title, List<StatementInput> idInputs, String idNameRule,
            boolean generateTriples, String idLabelRule, String whitespaceReplacementChar, boolean saveType,
            String errorWidgetId) {
        ArrayList<FStatementInput> idPageStatements = new ArrayList<FStatementInput>();

        String stepTitle = StringUtil.isNullOrEmpty(title) ? "Basic Properties" : title;

        for (int i = 0; i < idInputs.size(); i++) {
            StatementInput inp = idInputs.get(i);

            // create subject and predicate identifying the field
            URI idS = ValueFactoryImpl.getInstance().createURI("http://www.fluidops.com/id");

            URI idP = null;
            try {
                idP = EndpointImpl.api().getNamespaceService().guessURI(inp.predicate);
            } catch (Exception e) {
                return WidgetEmbeddingError.getErrorLabel(errorWidgetId, ErrorType.INVALID_WIDGET_CONFIGURATION,
                        e.getLocalizedMessage());
            }
            if (idP == null)
                return WidgetEmbeddingError.getErrorLabel(errorWidgetId, ErrorType.INVALID_WIDGET_CONFIGURATION,
                        "Value '" + inp.predicate + "' does not represent a valid URI");

            if (inp.type == null)
                return WidgetEmbeddingError.getErrorLabel(errorWidgetId, ErrorType.INVALID_WIDGET_CONFIGURATION,
                        "Type '" + inp.type + "' cannot be resolved");

            FStatementInput stinp = StatementInputHelper.getStatementInput("idinp" + i, idS, idP, null, false,
                    inp.type, inp.query, inp.values, rep);
            if (stinp == null)
                return WidgetEmbeddingError.getErrorLabel(errorWidgetId, ErrorType.INVALID_WIDGET_CONFIGURATION);
            else
                idPageStatements.add(stinp);
        }

        final FIdStatementInputGroup idGroup = new FIdStatementInputGroup("p" + getSteps().size(), idPageStatements,
                rep, idNameRule, generateTriples, idLabelRule, whitespaceReplacementChar, saveType,
                GroupLayout.INPUTS);

        // wrap step around the group
        FStatementInputGroupStep idStep = new FStatementInputGroupStep("s" + getSteps().size(), stepTitle,
                FMultiStageInputWizard.this, idGroup) {
            @Override
            public void onNext() throws Exception {
                // first check if all input fields are filled
                if (!idGroup.isComplete())
                    throw new Exception("Input form incomplete. Please fill in the missing fields.");
                FMultiStageInputWizard.this.setBaseURI(idGroup.getGeneratedURI(rep));
            }
        };
        addStep(idStep);
        interlinkSteps(idStep); // adjust prev/next pointers

        return null; // success
    }

    public FLabel appendOntologyStatementInputStep() {
        FOntologyInputGroup ontologyGroup;
        if (subject != null)
            ontologyGroup = new FOntologyInputGroup("p" + getSteps().size(), subject, rep,
                    GroupLayout.INPUTS_CLUSTERED_ADD);
        else
            // types!=null and not empty, as guaranteed by constructor
            ontologyGroup = new FOntologyInputGroup("p" + getSteps().size(), types, rep,
                    GroupLayout.INPUTS_CLUSTERED_ADD);

        FStatementInputGroupStep ontologyStep = new FStatementInputGroupStep("s" + getSteps().size(),
                "Ontology-defined Properties", FMultiStageInputWizard.this, ontologyGroup);

        addStep(ontologyStep);
        interlinkSteps(ontologyStep); // adjust prev/next pointers

        return null; // success
    }

    /**
     * Append the custom statements section to the wizard based on the provided
     * configuration.
     * 
     * @param title
     *          the title of the wizard page
     * @param customStatementInputs
     *          a list of {@link StatementInput} configurations
     * @param errorWidgetId
     *          the id of the error label (in case of errors)
     *          
     * @return
     *          an error label in case of errors or null if there was no error
     */
    public FLabel appendCustomStatementInputStep(String title, List<StatementInput> customStatementInputs,
            String errorWidgetId) {
        ArrayList<FStatementInput> customPageStatements = new ArrayList<FStatementInput>();

        String stepTitle = StringUtil.isNullOrEmpty(title) ? "Custom Properties" : title;

        // layout of predicates: single/multi => add single/multiple option
        Map<URI, GroupLayout> predicateLayout = new HashMap<URI, GroupLayout>();

        for (int i = 0; i < customStatementInputs.size(); i++) {
            StatementInput inp = customStatementInputs.get(i);

            inp.multiValue = inp.multiValue == null ? Boolean.TRUE : inp.multiValue;

            if (inp.predicate == null || inp.predicate.isEmpty())
                return WidgetEmbeddingError.getErrorLabel(errorWidgetId, ErrorType.INVALID_WIDGET_CONFIGURATION,
                        "empty custom predicate");

            URI predicate = null;
            try {
                predicate = EndpointImpl.api().getNamespaceService().guessURI(inp.predicate);
            } catch (Exception e) {
                return WidgetEmbeddingError.getErrorLabel(errorWidgetId, ErrorType.INVALID_WIDGET_CONFIGURATION,
                        e.getLocalizedMessage());
            }
            if (predicate == null)
                return WidgetEmbeddingError.getErrorLabel(errorWidgetId, ErrorType.INVALID_WIDGET_CONFIGURATION,
                        "Value '" + inp.predicate + "' does not represent a valid URI");

            if (inp.type == null)
                return WidgetEmbeddingError.getErrorLabel(errorWidgetId, ErrorType.INVALID_WIDGET_CONFIGURATION,
                        "Type '" + inp.type + "' cannot be resolved");

            FStatementInput stinp = StatementInputHelper.getStatementInput("cinp" + i, subject, predicate, null,
                    true, inp.type, inp.query, inp.values, rep);
            if (stinp == null) {
                return WidgetEmbeddingError.getErrorLabel(errorWidgetId, ErrorType.INVALID_WIDGET_CONFIGURATION);
            }
            customPageStatements.add(stinp);

            // handle group layout
            predicateLayout.put(predicate,
                    inp.multiValue ? GroupLayout.INPUTS_CLUSTERED_ADD : GroupLayout.INPUTS_CLUSTERED);
        }

        // fix layout
        GroupLayout layout = GroupLayout.INPUTS_CLUSTERED_ADD; // default
        FStatementInputGroup customGroup = new FStatementInputGroup("p" + getSteps().size(), customPageStatements,
                rep, layout, predicateLayout);

        FStatementInputGroupStep customStep = new FStatementInputGroupStep("s" + getSteps().size(), stepTitle,
                FMultiStageInputWizard.this, customGroup);

        addStep(customStep);
        interlinkSteps(customStep); // adjust prev/next pointers

        return null; // success
    }

    private void interlinkSteps(FStatementInputGroupStep curStep) {
        int nrOfSteps = getSteps().size();
        if (nrOfSteps > 1) {
            FStep previousStep = getSteps().get(nrOfSteps - 2);
            curStep.setPreviousStep(previousStep);
            if (previousStep instanceof FStatementInputGroupStep)
                ((FStatementInputGroupStep) previousStep).setNextStep(curStep);
        }
    }
}