Java tutorial
/* * 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); } } }