All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.shale.dialog.scxml.SCXMLDialogContext Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.shale.dialog.scxml;

import java.io.IOException;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.scxml.Context;
import org.apache.commons.scxml.SCXMLExecutor;
import org.apache.commons.scxml.SCXMLListener;
import org.apache.commons.scxml.TriggerEvent;
import org.apache.commons.scxml.env.SimpleDispatcher;
import org.apache.commons.scxml.env.SimpleErrorReporter;
import org.apache.commons.scxml.model.ModelException;
import org.apache.commons.scxml.model.SCXML;
import org.apache.commons.scxml.model.State;
import org.apache.commons.scxml.model.Transition;
import org.apache.commons.scxml.model.TransitionTarget;
import org.apache.shale.dialog.Constants;
import org.apache.shale.dialog.DialogContext;
import org.apache.shale.dialog.DialogContextListener;
import org.apache.shale.dialog.DialogContextManager;
import org.apache.shale.dialog.base.AbstractDialogContext;
import org.apache.shale.dialog.scxml.config.DialogMetadata;

/**
 * 

Implementation of {@link DialogContextManager} for integrating * Commons SCXML into the Shale Dialog Manager.

* * * @since 1.0.4 */ final class SCXMLDialogContext extends AbstractDialogContext implements Serializable { // ------------------------------------------------------------ Constructors /** * Serial version UID. */ private static final long serialVersionUID = 8423853327094172716L; /** *

Construct a new instance.

* * @param manager {@link DialogContextManager} instance that owns us * @param dialog The dialog's metadata (whose executable instance needs * to be created) * @param id Dialog identifier assigned to this instance * @param parentDialogId Dialog identifier assigned to the parent of * this instance */ SCXMLDialogContext(DialogContextManager manager, DialogMetadata dialog, String id, String parentDialogId) { this.manager = manager; this.name = dialog.getName(); this.dataClassName = dialog.getDataclassname(); this.id = id; this.parentDialogId = parentDialogId; // Create a working instance of the state machine for this dialog, but do not // set it in motion this.executor = new SCXMLExecutor(new ShaleDialogELEvaluator(), new SimpleDispatcher(), new SimpleErrorReporter()); SCXML statemachine = dialog.getStateMachine(); this.executor.setStateMachine(statemachine); Context rootCtx = new ShaleDialogELContext(); rootCtx.setLocal(Globals.DIALOG_PROPERTIES, new DialogProperties()); this.executor.setRootContext(rootCtx); this.executor.addListener(statemachine, new DelegatingSCXMLListener()); if (log().isDebugEnabled()) { log().debug("Constructor(id=" + id + ", name=" + name + ")"); } } // ------------------------------------------------------ DialogContext Variables /** *

Flag indicating that this {@link DialogContext} is currently active.

*/ private boolean active = true; /** *

Generic data object containing state information for this instance.

*/ private Object data = null; /** *

Type of data object (FQCN to be instantiated).

*/ private String dataClassName = null; /** *

Identifier of the parent {@link DialogContext} associated with * this {@link DialogContext}, if any. If there is no such parent, * this value is set to null.

*/ private String parentDialogId = null; /** *

Dialog identifier for this instance.

*/ private String id = null; /** *

{@link DialogContextManager} instance that owns us.

*/ private DialogContextManager manager = null; /** *

Logical name of the dialog to be executed.

*/ private String name = null; /** *

The {@link SCXMLExecutor}, an instance of the state machine * defined for the SCXML document for this dialog.

* */ private SCXMLExecutor executor = null; /** *

Flag indicating that execution has started for this dialog.

*/ private boolean started = false; /** *

The current SCXML state ID for this dialog instance, maintained * to reorient the dialog in accordance with any client-side navigation * between "view states" that may have happened since we last left off. * Serves as the "opaqueState" for this implementation.

*/ private String stateId = null; /** *

The Log instance for this dialog context. * This value is lazily created (or recreated) as necessary.

*/ private transient Log log = null; // ----------------------------------------------------- DialogContext Properties /** {@inheritDoc} */ public boolean isActive() { return this.active; } /** {@inheritDoc} */ public Object getData() { return this.data; } /** {@inheritDoc} */ public void setData(Object data) { Object old = this.data; if ((old != null) && (old instanceof DialogContextListener)) { removeDialogContextListener((DialogContextListener) old); } this.data = data; if ((data != null) && (data instanceof DialogContextListener)) { addDialogContextListener((DialogContextListener) data); } } /** {@inheritDoc} */ public String getId() { return this.id; } /** {@inheritDoc} */ public String getName() { return this.name; } /** {@inheritDoc} */ public Object getOpaqueState() { return stateId; } /** {@inheritDoc} */ public void setOpaqueState(Object opaqueState) { String viewStateId = String.valueOf(opaqueState); if (viewStateId == null) { throw new IllegalArgumentException("Dialog instance '" + getId() + "' for dialog name '" + getName() + "': null opaqueState received"); } // account for user agent navigation if (!viewStateId.equals(stateId)) { if (log().isTraceEnabled()) { log().trace("Dialog instance '" + getId() + "' of dialog name '" + getName() + "': user navigated to view for state '" + viewStateId + "', setting dialog to this state instead" + " of '" + stateId + "'"); } Map targets = executor.getStateMachine().getTargets(); State serverState = (State) targets.get(stateId); State clientState = (State) targets.get(viewStateId); if (clientState == null) { throw new IllegalArgumentException("Dialog instance '" + getId() + "' for dialog name '" + getName() + "': opaqueState is not a SCXML state ID for the " + "current dialog state machine"); } Set states = executor.getCurrentStatus().getStates(); if (states.size() != 1) { throw new IllegalStateException("Dialog instance '" + getId() + "' for dialog name '" + getName() + "': Cannot have multiple leaf states active when the" + " SCXML dialog is in a 'view' state"); } // remove last known server-side state, set to correct // client-side state and fire the appropriate DCL events states.remove(serverState); fireOnExit(serverState.getId()); fireOnEntry(clientState.getId()); states.add(clientState); } } /** {@inheritDoc} */ public DialogContext getParent() { if (this.parentDialogId != null) { DialogContext parent = manager.get(this.parentDialogId); if (parent == null) { throw new IllegalStateException("Dialog instance '" + parentDialogId + "' was associated with this instance '" + getId() + "' but is no longer available"); } return parent; } else { return null; } } // -------------------------------------------------------- DialogContext Methods /** {@inheritDoc} */ public void advance(FacesContext context, String outcome) { if (!started) { throw new IllegalStateException("Dialog instance '" + getId() + "' for dialog name '" + getName() + "' has not yet been started"); } if (log().isDebugEnabled()) { log().debug("advance(id=" + getId() + ", name=" + getName() + ", outcome=" + outcome + ")"); } // If the incoming outcome is null, we want to stay in the same // (view) state *without* recreating it, which would destroy // any useful information that components might have stored if (outcome == null) { if (log().isTraceEnabled()) { log().trace("advance(outcome is null, stay in same view)"); } return; } ((ShaleDialogELEvaluator) executor.getEvaluator()). setFacesContext(context); executor.getRootContext().setLocal(Globals.POSTBACK_OUTCOME, outcome); try { executor.triggerEvent(new TriggerEvent(Globals.POSTBACK_EVENT, TriggerEvent.SIGNAL_EVENT)); } catch (ModelException me) { fireOnException(me); } Iterator iterator = executor.getCurrentStatus().getStates().iterator(); this.stateId = ((State) iterator.next()).getId(); DialogProperties dp = (DialogProperties) executor.getRootContext(). get(Globals.DIALOG_PROPERTIES); // If done, stop context if (executor.getCurrentStatus().isFinal()) { stop(context); } navigateTo(stateId, context, dp); } /** {@inheritDoc} */ public void start(FacesContext context) { if (started) { throw new IllegalStateException("Dialog instance '" + getId() + "' for dialog name '" + getName() + "' has already been started"); } started = true; if (log().isDebugEnabled()) { log().debug("start(id=" + getId() + ", name=" + getName() + ")"); } // inform listeners we're good to go fireOnStart(); // Construct an appropriate data object for the specified dialog ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (loader == null) { loader = SCXMLDialogContext.class.getClassLoader(); } Class dataClass = null; try { dataClass = loader.loadClass(dataClassName); data = dataClass.newInstance(); } catch (Exception e) { fireOnException(e); } if (data != null && data instanceof DialogContextListener) { addDialogContextListener((DialogContextListener) data); } // set state machine in motion ((ShaleDialogELEvaluator) executor.getEvaluator()). setFacesContext(context); try { executor.go(); } catch (ModelException me) { fireOnException(me); } Iterator iterator = executor.getCurrentStatus().getStates().iterator(); this.stateId = ((State) iterator.next()).getId(); DialogProperties dp = (DialogProperties) executor.getRootContext(). get(Globals.DIALOG_PROPERTIES); // Might be done at the beginning itself, if so, stop context if (executor.getCurrentStatus().isFinal()) { stop(context); } navigateTo(stateId, context, dp); } /** {@inheritDoc} */ public void stop(FacesContext context) { if (!started) { throw new IllegalStateException("Dialog instance '" + getId() + "' for dialog name '" + getName() + "' has not yet been started"); } started = false; if (log().isDebugEnabled()) { log().debug("stop(id=" + getId() + ", name=" + getName() + ")"); } deactivate(); manager.remove(this); // inform listeners fireOnStop(); } // ------------------------------------------------- Package Private Methods /** *

Mark this {@link DialogContext} as being deactivated. This should only * be called by the remove() method on our associated * {@link DialogContextManager}.

*/ void deactivate() { setData(null); this.active = false; } // ------------------------------------------------- Private Methods /** *

Navigate to the JavaServer Faces view identifier * that is mapped to by the current state identifier for this dialog.

* * @param stateId The current state identifier for this dialog. * @param context The current FacesContext * @param dp The DialogProperties for the current dialog */ private void navigateTo(String stateId, FacesContext context, DialogProperties dp) { // Determine the view identifier String viewId = dp.getNextViewId(); if (viewId == null) { ValueBinding vb = context.getApplication().createValueBinding ("#{" + Globals.STATE_MAPPER + "}"); DialogStateMapper dsm = (DialogStateMapper) vb.getValue(context); viewId = dsm.mapStateId(name, stateId, context); } else { dp.setNextViewId(null); // one time use } // Navigate to the requested view identifier (if any) if (viewId == null) { return; } if (!viewId.startsWith("/")) { viewId = "/" + viewId; } // The public API is advance, so thats part of the message if (log().isDebugEnabled()) { log().debug("advance(id=" + getId() + ", name=" + getName() + ", navigating to view: '" + viewId + "')"); } ViewHandler vh = context.getApplication().getViewHandler(); if (dp.isNextRedirect()) { // clear redirect flag dp.setNextRedirect(false); String actionURL = vh.getActionURL(context, viewId); if (actionURL.indexOf('?') < 0) { actionURL += '?'; } else { actionURL += '&'; } actionURL += Constants.DIALOG_ID + "=" + this.id; try { ExternalContext econtext = context.getExternalContext(); econtext.redirect(econtext.encodeActionURL(actionURL)); context.responseComplete(); } catch (IOException e) { throw new FacesException("Cannot redirect to " + actionURL, e); } } else { UIViewRoot view = vh.createView(context, viewId); view.setViewId(viewId); context.setViewRoot(view); context.renderResponse(); } } /** *

Return the Log instance for this dialog context, * creating one if necessary.

* * @return The log instance. */ private Log log() { if (log == null) { log = LogFactory.getLog(SCXMLDialogContext.class); } return log; } /** * A {@link SCXMLListener} that delegates to the Shale * {@link DialogContextListener}s attached to this {@link DialogContext}. */ class DelegatingSCXMLListener implements SCXMLListener, Serializable { /** * Serial version UID. */ private static final long serialVersionUID = 1L; /** * Handle entry callbacks. * * @param tt The TransitionTarget being entered. */ public void onEntry(TransitionTarget tt) { fireOnEntry(tt.getId()); } /** * Handle transition callbacks. * * @param from The source TransitionTarget * @param to The destination TransitionTarget * @param t The Transition */ public void onTransition(TransitionTarget from, TransitionTarget to, Transition t) { fireOnTransition(from.getId(), to.getId()); } /** * Handle exit callbacks. * * @param tt The TransitionTarget being exited. */ public void onExit(TransitionTarget tt) { fireOnExit(tt.getId()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy