com.sun.webui.jsf.model.WizardModelBase Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2007-2018 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://oss.oracle.com/licenses/CDDL+GPL-1.1
* or LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.webui.jsf.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import javax.faces.context.FacesContext;
import com.sun.webui.jsf.event.WizardEvent;
import com.sun.webui.jsf.component.Wizard;
import com.sun.webui.jsf.component.WizardBranch;
import com.sun.webui.jsf.component.WizardBranchSteps;
import com.sun.webui.jsf.component.WizardStep;
import com.sun.webui.jsf.component.WizardSubstepBranch;
/**
* WizardModelBase
is the default
* {@link WizardModel WizardModel} instance used by the
* {@link Wizard Wizard}.
* This class's behavior supports wizard functionality as defined
* by "Web Application Guidelines - Version 3.0". The support for a
* step list as defined by the guidelines is encapsulated in
* {@link WizardStepListBase WizardStepListBase}.
* This class maintains state, controls navigation, and expects the
* existence of finish
and results
wizard steps.
* In addition this class can control whether or not steps should participate
* in JSF lifecycle phases, see
* {@link WizardModelBase#decode(int, boolean) decode},
* {@link WizardModelBase#validate(int, boolean) validate}, and
* {@link WizardModelBase#update(int, boolean) update}.
*
* WizardModelBase
expects a hierachy of
* {@link WizardStep WizardStep}, {@link WizardBranch WizardBranch},
* {@link WizardBranchSteps WizardBranchSteps}, and
* {@link WizardSubstepBranch WizardSubstepBranch}
* components. This hierarchy can be specified as children of the
* {@link Wizard Wizard} component or as a list returned by
* {@link Wizard#getSteps() Wizard.getSteps()}. If
* {@link Wizard#getSteps() getSteps} does not
* return null, the steps represented by that value takes precedence over the
* existence of {@link Wizard Wizard} child components.
*
*
* The {@link Wizard Wizard} component calls the {@link #initialize(Wizard)
* initialize} method twice during the request lifecycle. Once during
* the RESTORE_VIEW phase, and once during the RENDER_RESPONSE phase.
* This is necessary
* so the model can re-evaluate the steps if changes have occured during
* request processing or in the INVOKE_APPLICATION phase, which occur before
* the RENDER_RESPONSE phase. On the first display of a wizard, the
* {@link #initialize(Wizard) initialize} method is only called once
* during the RENDER_RESPONSE phase.
* Note that a call to initialize during RESTORE_VIEW phase is only relevant
* when the application is configured in client side state saving mode.
*
*
* When the wizard receives events due to a user clicking one of the navigation
* components, it calls {@link WizardModelBase#handleEvent(WizardEvent)
* handleEvent} with the corresponding event and data.
*
*
* The wizard will call the {@link WizardModelBase#complete()
* complete} method to indicate
* to the model that it considers the wizard session to be over and will no
* longer reference it during this wizard session,
* and that it is free to deallocate any resources.
*
*/
public class WizardModelBase implements WizardModel {
/**
* The current step sequence of {@link WizardStep WizardStep} instances.
*/
private ArrayList wizardSteps;
/**
* The current model state.
*/
private WizardState wizardState;
/**
* The current step list of
* {@link WizardStepListItem WizardStepListItem} instances.
*/
private WizardStepList wizardStepList;
/**
* Construct a WizardModelBase
instance.
* The initialize
method must be called with the
* wizard
instance when the step children are
* available. The Wizard
instance will call this method
* at the appropriate time.
*/
public WizardModelBase() {
super();
wizardState = new WizardState();
}
/**
* Initialize the current set of WizardSteps.
* This method is called by the {@link Wizard Wizard} twice. Once
* during RETORE_VIEW phase, and once during RENDER_RESPONSE phase.
* This sequence affords the model the opportunity to modify its
* state due to changes that may have occured during request processing.
*
* Note that a call during RESTORE_VIEW phase is only relevant
* when the application is configured in client side state saving mode.
*
* @param wizard The Wizard component.
*/
public void initialize(Wizard wizard) {
wizardSteps = new ArrayList();
// Steps can be obtained from both "getSteps" or
// as children of Wizard. This model uses one or the
// other. If wizard.getSteps() does not return null
// it uses that list, if it returns null it checks for
// wizard children and uses that list.
//
List steps = (List) wizard.getSteps();
if (steps == null) {
steps = (List) wizard.getChildren();
}
ListIterator childIterator = steps.listIterator();
buildStepList(childIterator, wizardSteps);
wizardStepList = new WizardStepListBase(this);
}
// Call recursively to collect substep and branch children
//
/**
* This method builds the current step sequence based on the state
* of the wizard.
* The current sequence is determined by the values returned from calls to
* the isTaken method on {@link WizardBranch WizardBranch},
* {@link WizardBranchSteps WizardBranchSteps} and
* {@link WizardSubstepBranch WizardSubstepBranch} instances in the
* step hierarchy.
* The current step sequence is constructed according to the
* "Web Application Guidelines - Version 3.0".
* For example if a {@link WizardBranch WizardBranch} is encountered,
* the step sequence will end with this step. According to the
* guidelines, a branch indicates that proceeding steps cannot be
* determined until data is collected before this branch step is
* reached. When the step hierarchy is evaluated again and the branch
* isTaken method returns true, then the steps for that particular branch
* are added to the sequence.
*
* This method also determines if there is step help available.
*
*
* @param childIterator contains all the steps of the wizard.
* @param wizardSteps ArrayList to contain the steps that are in the
* current sequence.
*/
protected void buildStepList(ListIterator childIterator,
ArrayList wizardSteps) {
Object step = null;
while (childIterator.hasNext()) {
step = childIterator.next();
if (step instanceof WizardBranch) {
WizardBranch branch = (WizardBranch) step;
if (branch.isTaken()) {
buildStepList(branch.getChildren().listIterator(),
wizardSteps);
} else {
// If the branch hasn't been taken then
// this is the last step until it is taken.
//
wizardSteps.add(step);
break;
}
} else if (step instanceof WizardBranchSteps) {
WizardBranchSteps branchSteps = (WizardBranchSteps) step;
if (branchSteps.isTaken()) {
buildStepList(branchSteps.getChildren().listIterator(),
wizardSteps);
}
} else if (step instanceof WizardSubstepBranch) {
WizardSubstepBranch substep = (WizardSubstepBranch) step;
if (substep.isTaken()) {
buildStepList(substep.getChildren().listIterator(),
wizardSteps);
}
} else if (step instanceof WizardStep) {
// If even one WizardStep has help, show help
//
if (wizardState.getHasStepHelp() == null) {
if (((WizardStep) step).getHelp() != null) {
wizardState.setHasStepHelp(Boolean.TRUE);
}
}
wizardSteps.add(step);
}
}
}
/**
* Based on the current state of the Wizard, return an Iterator
* of the current sequence of {@link WizardStep WizardStep} instances.
*/
public Iterator getWizardStepIterator() {
return wizardSteps.iterator();
}
/**
* Return a {@link WizardStepList WizardStepList} of
* {@link WizardStepListItem WizardStepListItem} instances.
*/
public WizardStepList getWizardStepList() {
return wizardStepList;
}
/**
* Return the first {@link WizardStep WizardStep} instance.
*/
public WizardStep getFirstStep() {
return (WizardStep) wizardSteps.get(0);
}
/**
* Return the last {@link WizardStep WizardStep} instance.
* The step that is returned is dependent on the state of the
* wizard. It may only be the last step at this point in time.
* At a later point in time, it may be a different step, due to
* the existence of branch steps.
*/
public WizardStep getLastStep() {
return (WizardStep) wizardSteps.get(wizardSteps.size() - 1);
}
/**
* Return the index into an ArrayList
of
* {@link WizardStep WizardStep} instances, for the step with
* id
.
*
* @param id The id of a {@link WizardStep WizardStep} instance.
*/
protected int getStepIndex(String id) {
for (int i = 0; i < wizardSteps.size(); ++i) {
WizardStep step = (WizardStep) wizardSteps.get(i);
if (id.equals(step.getId())) {
return i;
}
}
// What else ?
// Need to implement errors to component
// alerts ? FacesMessage ?
return wizardState.getCurrentStep();
}
/**
* Return the {@link WizardStep WizardStep} instance following
* step
.
* If there are no more steps return null.
*
* @param step The step preceding the returned step.
*/
public WizardStep getNextStep(WizardStep step) {
WizardStep next = null;
try {
int i = wizardSteps.indexOf(step);
if (i != -1) {
next = (WizardStep) wizardSteps.get(++i);
}
} catch (Exception e) {
}
return next;
}
/**
* Return the {@link WizardStep WizardStep} instance preceding
* step
* If there is no previous step return null.
*
* @param step The step following the returned step.
*/
public WizardStep getPreviousStep(WizardStep step) {
WizardStep previous = null;
try {
int i = wizardSteps.indexOf(step);
if (i != -1) {
previous = (WizardStep) wizardSteps.get(--i);
}
} catch (Exception e) {
}
return previous;
}
/**
* Return the current {@link WizardStep WizardStep} instance.
*/
public WizardStep getCurrentStep() {
WizardStep step = null;
try {
step = (WizardStep) wizardSteps.get(wizardState.getCurrentStep());
} catch (Exception e) {
}
return step;
}
/**
* Return true if step
is the current step, else false.
*
* @param step The step to test.
*/
public boolean isCurrentStep(WizardStep step) {
boolean result = false;
try {
int i = wizardSteps.indexOf(step);
result = wizardState.getCurrentStep() == i;
} catch (Exception e) {
}
return result;
}
/**
* Return true if step
is the step the should
* contain a Finish button. This is the step that performs the
* wizard task with the data collected from previous steps.
*
* @param step The step to identify.
*/
public boolean isFinishStep(WizardStep step) {
return step == null ? false : step.isFinish();
}
/**
* Return true if step is the results step, else false.
* The Results step follows the Finish step and displays
* only a "Close" button. It displays results of the task
* performed in the Finish step.
*
* @param step The step to identify.
*/
public boolean isResultsStep(WizardStep step) {
return step == null ? false : step.isResults();
}
/**
* Return true if step is a branching step, else false.
* A branching step acts as a step "placeholder" and informs
* the user that the steps following this step are determined by
* the data entered in this or previous steps. Text should be
* provided from the {@link #getPlaceholderText(WizardStep)
* getPlaceHolderText} method for this step, describing the branch.
*
* @param step The step to identify.
*/
public boolean isBranch(WizardStep step) {
return step instanceof WizardBranch;
}
/**
* Return a description of this {@link WizardBranch WizardBranch} step.
*
* @param step A branching step. It must be a
* {@link WizardBranch WizardBranch} instance.
*/
public String getPlaceholderText(WizardStep step) {
return step == null ? null : ((WizardBranch) step).getPlaceholderText();
}
/**
* Return true if step is a substep step, else false.
* A substep is a step or one of a series of substeps,
* that occurs in every instance of this wizard.
* Unlike the branch step, substep sequences are always the same
* but may or may not be performed based on previous steps.
*
* @param step The step to check.
*/
public boolean isSubstep(WizardStep step) {
return step == null ? false : step.getParent() instanceof WizardSubstepBranch;
}
/**
* Return true if the user can navigate to step out
* of sequence, else false.
* Typically this method is called to determine if a previous
* step should be rendered such that the user can select it
* and navigate back to that step. Its possible that some
* wizards may also allow forward navigation.
*
* Note this method only supports navigating to steps occuring
* before the current step.
*
*
* @param step The step to check.
*/
public boolean canGotoStep(WizardStep step) {
try {
int i = wizardSteps.indexOf(step);
return wizardState.getCurrentStep() > i;
} catch (Exception e) {
return false;
}
}
/**
* Return true if the previous button should be disabled
* for this step, else false. Typically the first step of a
* sequence should return true, since there usually isn't a
* step before the first step.
*
* @param step The step to check.
*/
public boolean isPreviousDisabled(WizardStep step) {
WizardStep first = getFirstStep();
return first == null ? false : first.getId().equals(step.getId());
}
/**
* Return true if the next button should be disabled
* for this step, else false.
* This method always returns false;
*
* @param step The step to check.
*/
public boolean isNextDisabled(WizardStep step) {
return false;
}
/**
* Return true if the finish button should be disabled
* for this step, else false.
* This method always returns false;
*
* @param step The step to check.
*/
public boolean isFinishDisabled(WizardStep step) {
return false;
}
/**
* Return true if the cancel button should be disabled
* for this step, else false.
* This method always returns false;
*
* @param step The step to check.
*/
public boolean isCancelDisabled(WizardStep step) {
return false;
}
/**
* Return true if the close button should be disabled
* for this step, else false.
* This method always returns false;
*
* @param step The step to check.
*/
public boolean isCloseDisabled(WizardStep step) {
return false;
}
/**
* Return true if the previous button should be rendered
* for this step, else false. Typically this method returns
* true for all steps except for steps that are results steps.
*
* @param step The step to check.
*/
public boolean hasPrevious(WizardStep step) {
return step == null ? false : !isResultsStep(step);
}
/**
* Return true if the next button should be rendered
* for this step, else false. Typically this method returns
* true for all steps except for steps that are finish or
* results steps.
*
* @param step The step to check.
*/
public boolean hasNext(WizardStep step) {
return step == null ? false : !(isFinishStep(step) || isResultsStep(step));
}
/**
* Return true if the cancel button should be rendered
* for this step, else false. Typically this method returns
* true for all steps except for steps that are results steps.
*
* @param step The step to check.
*/
public boolean hasCancel(WizardStep step) {
return step == null ? false : !isResultsStep(step);
}
/**
* Return true if the close button should be rendered
* for this step, else false. Typically this method returns
* true only for the results step.
*
* @param step The step to check.
*/
public boolean hasClose(WizardStep step) {
return step == null ? false : isResultsStep(step);
}
/**
* Return true if the finish button should be rendered
* for this step, else false. Typically this method returns
* true only for the finish step.
*
* @param step The step to check.
*/
public boolean hasFinish(WizardStep step) {
// For now we only support simple linear wizards with no
// results page.
//
return step == null ? false : isFinishStep(step);
}
/**
* Return true if any of the steps have step help.
* If any of the steps have step help, this method should return
* true, unless no step help should be shown for the wizard.
* If the determination had not been made when this method is
* called, since the step list must be built at least once,
* false is returned.
*/
public boolean hasStepHelp() {
return wizardState.getHasStepHelp() == null ? false : wizardState.getHasStepHelp().booleanValue();
}
/**
* Return true if the wizard has completed and there are no
* more steps for the user to complete, else false. Typically
* this informs the wizard that there is nothing more to render.
* This may cause a popup wizard to be dismissed or an inline
* wizard to navigate to some other page.
*/
public boolean isComplete() {
return wizardState.isComplete();
}
/**
* Called to inform the model that this instance will no longer be
* referenced.
*/
public void complete() {
wizardSteps = null;
wizardState.reset();
}
/**
* Returns false if prematureRender is true, else true if
* the step should participate in the APPLY_REQUEST_VALUES phase.
*
* @param event The event that precipitated this call.
* @param prematureRender true if rendering is occuring before
* RENDER_RESPONSE phase was normally expected.
*/
public boolean decode(int event, boolean prematureRender) {
return prematureRender ? false : wizardState.decode(event);
}
/**
* Return true if the current step should participate in the
* PROCESS_VALIDATIONS phase.
* Returns true if event is WizardEvent.NEXT or WizardEvent.FINISH
* and prematureRender is false.
*
* @param event The event that precipitated this call.
* @param prematureRender Is true if rendering is occuring before
* RENDER_RESPONSE phase was normally expected.
*/
public boolean validate(int event, boolean prematureRender) {
return prematureRender ? false : wizardState.validate(event);
}
/**
* Return true if the current step should participate in the
* UPDATE_MODEL_VALUES phase.
* Returns true if event is WizardEvent.NEXT or WizardEvent.FINISH and
* prematureRender is false.
*
* @param event The event that precipitated this call.
* @param prematureRender true if rendering is occuring before
* RENDER_RESPONSE phase was normally expected.
*/
public boolean update(int event, boolean prematureRender) {
return prematureRender ? false : wizardState.update(event);
}
/**
* Handle the following {@link WizardEvent WizardEvent} events
* and adjust the state accordingly.
*
* - WizardEvent.CANCEL
* - WizardEvent.CLOSE
* - WizardEvent.FINISH
* - WizardEvent.GOTOSTEP
* - WizardEvent.HELPTAB
* - WizardEvent.NEXT
* - WizardEvent.PREVIOUS
* - WizardEvent.STEPSTAB
*
*/
public boolean handleEvent(WizardEvent event) {
boolean returnValue = true;
switch (event.getNavigationEvent()) {
case WizardEvent.NEXT:
wizardState.nextStep();
break;
case WizardEvent.PREVIOUS:
wizardState.previousStep();
break;
case WizardEvent.FINISH:
int i = wizardState.getCurrentStep() + 1;
if (i == wizardSteps.size()) {
wizardState.close();
} else {
wizardState.finishStep();
}
break;
case WizardEvent.GOTOSTEP:
String gotoStepId = event.getGotoStepId();
if (gotoStepId != null) {
int index = getStepIndex(gotoStepId);
wizardState.gotoStep(index);
}
break;
case WizardEvent.CANCEL:
wizardState.cancel();
break;
case WizardEvent.CLOSE:
wizardState.close();
break;
case WizardEvent.HELPTAB:
break;
case WizardEvent.STEPSTAB:
break;
case WizardEvent.INVALID:
break;
case WizardEvent.NOEVENT:
break;
}
return returnValue;
}
/**
* This class maintains the current step and most recent
* navigation event received from the Wizard component.
* Is stores a simple index into the wizardSteps ArrayList
*
* It defines the state that controls the decode, validate and
* update methods of the WizardModel.
*/
class WizardState {
static final int START = -1;
static final int NEXT = 0;
static final int PREVIOUS = 1;
static final int CANCEL = 2;
static final int FINISH = 3;
static final int CLOSE = 4;
static final int GOTOSTEP = 7;
int state;
int currentStep;
Boolean hasStepHelp;
WizardState() {
super();
this.state = START;
this.currentStep = 0;
}
int getState() {
return state;
}
void setState(int state) {
this.state = state;
}
Boolean getHasStepHelp() {
return hasStepHelp;
}
void setHasStepHelp(Boolean hasStepHelp) {
this.hasStepHelp = hasStepHelp;
}
boolean isComplete() {
return state == CLOSE || state == CANCEL;
}
boolean decode(int event) {
return true;
}
boolean validate(int event) {
return event == WizardEvent.FINISH ||
event == WizardEvent.NEXT ||
event == WizardEvent.NOEVENT;
}
// Its not clear if another state is needed for
// updating on the previous click. We want the
// state but not the commit.
//
boolean update(int event) {
return event == WizardEvent.FINISH ||
event == WizardEvent.NEXT ||
event == WizardEvent.NOEVENT;
}
void nextStep() {
state = NEXT;
++currentStep;
}
void finishStep() {
state = FINISH;
++currentStep;
}
void previousStep() {
state = PREVIOUS;
--currentStep;
}
void gotoStep(int step) {
state = GOTOSTEP;
currentStep = step;
}
int getCurrentStep() {
return currentStep;
}
void setCurrentStep(int currentStep) {
this.currentStep = currentStep;
}
void reset() {
state = START;
currentStep = 0;
}
void cancel() {
this.state = CANCEL;
}
void close() {
this.state = CLOSE;
}
};
/**
* StateHolder
method called to save the state the model's
* state.
* The saved state consists of the values of state,
* currentStep
, and hasStepHelp
of the internal
* WizardState
instance.
*/
public Object saveState(FacesContext context) {
Object[] state = new Object[3];
int i = 0;
state[i++] = new Integer(wizardState.getState());
state[i++] = new Integer(wizardState.getCurrentStep());
state[i++] = wizardState.getHasStepHelp();
return state;
}
/**
* StateHolder
method called to restore the model's
* current state based on the values in state
.
* The restored state consists of values for state,
* currentStep
, and hasStepHelp
of the internal
* WizardState
instance.
*/
public void restoreState(FacesContext context, Object state) {
Object[] _state = (Object[]) state;
int i = 0;
wizardState.setState(((Integer) _state[i++]).intValue());
wizardState.setCurrentStep(((Integer) _state[i++]).intValue());
wizardState.setHasStepHelp((Boolean) _state[i++]);
}
/**
* StateHolder
method to set the persistent state of this
* class. This call does not modify this class.
*/
public void setTransient(boolean transientFlag) {
// ignore this
}
/**
* StateHolder
method indicating the this class is
* persistent. This method returns false
*/
public boolean isTransient() {
return false;
}
}