/**
* Copyright (C) 2010 Orbeon, Inc.
*
* This program 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 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 Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.xforms.model;
import org.orbeon.dom.Element;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.common.OrbeonLocationException;
import org.orbeon.oxf.common.ValidationException;
import org.orbeon.oxf.externalcontext.ExternalContext;
import org.orbeon.oxf.processor.ProcessorImpl;
import org.orbeon.oxf.util.*;
import org.orbeon.oxf.xforms.*;
import org.orbeon.oxf.xforms.analysis.ElementAnalysis;
import org.orbeon.oxf.xforms.analysis.model.Instance;
import org.orbeon.oxf.xforms.analysis.model.Model;
import org.orbeon.oxf.xforms.analysis.model.Submission;
import org.orbeon.oxf.xforms.control.Controls;
import org.orbeon.oxf.xforms.event.*;
import org.orbeon.oxf.xforms.event.events.*;
import org.orbeon.oxf.xforms.state.InstanceState;
import org.orbeon.oxf.xforms.submission.BaseSubmission;
import org.orbeon.oxf.xforms.submission.SubmissionUtils;
import org.orbeon.oxf.xforms.submission.XFormsModelSubmission;
import org.orbeon.oxf.xforms.xbl.Scope;
import org.orbeon.oxf.xforms.xbl.XBLContainer;
import org.orbeon.oxf.xml.TransformerUtils;
import org.orbeon.oxf.xml.dom4j.ExtendedLocationData;
import org.orbeon.oxf.xml.dom4j.LocationData;
import org.orbeon.saxon.om.*;
import org.orbeon.saxon.trans.XPathException;
import org.orbeon.saxon.value.Value;
import scala.Option;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/**
* Represents an XForms model.
*/
public class XFormsModel extends XFormsModelBase implements XFormsEventObserver, XFormsObjectResolver {
public static final String LOGGING_CATEGORY = "model";
// Static representation of this model
public final Model staticModel;
// Model attributes
private String effectiveId; // not final because can change if model within repeat iteration
// Instances
private final List<String> instanceIds;
private final List<XFormsInstance> instances;
private final Map<String, XFormsInstance> instancesMap;
// Submissions and actions
private final Map<String, XFormsModelSubmission> submissions;
private final Map<String, XFormsModelAction> actions = new HashMap<String, XFormsModelAction>();
// Context and variables
private BindingContext defaultEvaluationContext;
private Map<String, ValueRepresentation> topLevelVariables = new LinkedHashMap<String, ValueRepresentation>();
// Binds
private final Option<XFormsModelBinds> _modelBindsOpt;
// Container
private final XBLContainer container;
private final XFormsContextStack contextStack; // context stack for evaluation, used by binds, submissions, event handlers
public XFormsModel(XBLContainer container, String effectiveId, Model staticModel) {
super(container, effectiveId, staticModel);
// Remember static model
this.staticModel = staticModel;
// Set container
this.container = container;
this.effectiveId = effectiveId;
// Extract list of instances ids
{
final Collection<Instance> staticInstances = staticModel.instancesMap().values();
if (staticInstances.isEmpty()) {
// No instance in this model
instanceIds = Collections.emptyList();
instances = Collections.emptyList();
instancesMap = Collections.emptyMap();
} else {
// At least one instance in this model
instanceIds = new ArrayList<String>(staticInstances.size());
for (final Instance instance : staticInstances)
instanceIds.add(instance.staticId());
instances = Arrays.asList(new XFormsInstance[staticInstances.size()]);
instancesMap = new HashMap<String, XFormsInstance>(staticInstances.size());
}
}
// Get submissions
{
final List<Submission> staticSubmissions = staticModel.jSubmissions();
if (staticSubmissions.isEmpty()) {
// No submission in this model
submissions = Collections.emptyMap();
} else {
// At least one submission in this model
submissions = new HashMap<String, XFormsModelSubmission>();
for (final Submission staticSubmission : staticSubmissions)
submissions.put(staticSubmission.staticId(), new XFormsModelSubmission(this.container, staticSubmission, this));
}
}
// Get all event handlers
for (final EventHandlerImpl staticEventHandler : staticModel.jEventHandlers()) {
final ElementAnalysis staticParent = staticEventHandler.parent().get();
final XFormsEventObserver parent;
if (staticParent instanceof Submission) {
parent = submissions.get(staticParent.staticId());
} else {
parent = XFormsModel.this;
}
actions.put(staticEventHandler.staticId(), new XFormsModelAction(parent, staticEventHandler));
}
// Create binds object
_modelBindsOpt = XFormsModelBinds.apply(this);
// Create context stack
this.contextStack = new XFormsContextStack(container());
// Temporarily initialize the evaluation context to an empty context, so that handlers upon xforms-model-construct can work
this.defaultEvaluationContext = XFormsContextStack.defaultContext(null, container, this);
}
// Evaluate all top-level variables
public void resetAndEvaluateVariables() {
// NOTE: This method is called during RRR and by submission processing. Need to do dependency handling.
// Reset context to this model, including evaluating the model variables
contextStack.resetBindingContext(this);
// Remember context and variables
defaultEvaluationContext = contextStack.getCurrentBindingContext();
}
// Return the value of the given model variable
public SequenceIterator getVariable(String variableName) throws XPathException {
return Value.asIterator(topLevelVariables.get(variableName));
}
public void updateEffectiveId(String effectiveId) {
this.effectiveId = effectiveId;
}
public String getPrefixedId() {
return staticModel.prefixedId();
}
public IndentedLogger getIndentedLogger() {
return indentedLogger();
}
public XFormsContextStack getContextStack() {
return contextStack;
}
public XBLContainer container() {
return container;
}
public XFormsModel selfModel() { return this; }
public Model getStaticModel() {
return staticModel;
}
/**
* Get object with the id specified.
*/
public XFormsObject getObjectByEffectiveId(String effectiveId) {
// If prefixes or suffixes don't match, object can't be found here
if (!container().getFullPrefix().equals(XFormsUtils.getEffectiveIdPrefix(effectiveId))
|| !XFormsUtils.getEffectiveIdSuffix(container().getEffectiveId()).equals(XFormsUtils.getEffectiveIdSuffix(effectiveId))) {
return null;
}
// Find by static id
return resolveObjectById(null, XFormsUtils.getStaticIdFromId(effectiveId), null);
}
/**
* Resolve an object. This optionally depends on a source, and involves resolving whether the source is within a
* repeat or a component.
*
* @param sourceEffectiveId effective id of the source, or null
* @param targetStaticId static id of the target
* @param contextItem context item, or null (used for bind resolution only)
* @return object, or null if not found
*/
public XFormsObject resolveObjectById(String sourceEffectiveId, String targetStaticId, Item contextItem) {
if (XFormsUtils.isEffectiveId(targetStaticId) || XFormsUtils.isAbsoluteId(targetStaticId))
throw new OXFException("Target id must be static id: " + targetStaticId);
// Check this id
if (targetStaticId.equals(getId()))
return this;
// Search instances
final XFormsInstance instance = instancesMap.get(targetStaticId);
if (instance != null)
return instance;
// Search submissions
if (submissions != null) {
final XFormsModelSubmission resultSubmission = submissions.get(targetStaticId);
if (resultSubmission != null)
return resultSubmission;
}
// Search actions
{
final XFormsModelAction action = actions.get(targetStaticId);
if (action != null)
return action;
}
// Search binds
if (_modelBindsOpt.isDefined()) {
final RuntimeBind bind = _modelBindsOpt.get().resolveBind(targetStaticId, contextItem);
if (bind != null)
return bind;
}
return null;
}
/**
* Return the default instance for this model, i.e. the first instance. Return null if there is
* no instance in this model.
*
* @return XFormsInstance or null
*/
public XFormsInstance getDefaultInstance() {
return ! instances.isEmpty() ? instances.get(0) : null;
}
public Option<XFormsInstance> defaultInstanceOpt() {
return ! instances.isEmpty() ? scala.Option.apply(instances.get(0)) : scala.Option.apply((XFormsInstance) null);
}
/**
* Return all XFormsInstance objects for this model, in the order they appear in the model.
*/
public List<XFormsInstance> getInstances() {
// TODO: Some instances can be uninitialized during model construction. Callers should test for that,
// or we should filter by non-null instances.
return instances;
}
/**
* Return the XFormsInstance with given id, null if not found.
*/
public XFormsInstance getInstance(String instanceStaticId) {
return instancesMap.get(instanceStaticId);
}
/**
* Return the XFormsInstance object containing the given node.
*/
public XFormsInstance getInstanceForNode(NodeInfo nodeInfo) {
final DocumentInfo documentInfo = nodeInfo.getDocumentRoot();
// NOTE: We shouldn't even be called if the parent control is not relevant.
if (container.isRelevant()) {
// NOTE: Some instances can be uninitialized during model construction so test for null.
for (final XFormsInstance currentInstance : instances) {
if (currentInstance != null && currentInstance.documentInfo().isSameNodeInfo(documentInfo))
return currentInstance;
}
}
return null;
}
/**
* Set an instance. The id of the instance must exist in the model.
*/
public void indexInstance(XFormsInstance instance) {
final String instanceId = instance.instance().staticId();
final int instancePosition = instanceIds.indexOf(instanceId);
instances.set(instancePosition, instance);
instancesMap.put(instanceId, instance);
}
public String getId() {
return staticModel.staticId();
}
public String getEffectiveId() {
return effectiveId;
}
public Scope scope() {
return staticModel.scope();
}
public Scope getResolutionScope() {
return container.getPartAnalysis().scopeForPrefixedId(getPrefixedId());
}
public LocationData getLocationData() {
return staticModel.locationData();
}
public XFormsModelBinds getBinds() {
return _modelBindsOpt.isDefined() ? _modelBindsOpt.get() : null;
}
public Option<XFormsModelBinds> modelBindsOpt() {
return _modelBindsOpt;
}
/**
* Restore the state of the model when the model object was just recreated.
*/
public void restoreState(boolean deferRRR) {
// Ensure schema are loaded
schemaValidator();
// Refresh binds, but do not recalculate (only evaluate "computed expression binds")
// TODO: We used to not redo recalculations upon state restore. Does this cause a problem? Shouldn't
// recalculations not depend on the number of times they run anyway?
deferredActionContext().jMarkStructuralChange();
if (! deferRRR) {
doRebuild();
doRecalculateRevalidate();
}
}
/**
* Restore all the instances serialized as children of the given container element.
*/
public void restoreInstances() {
// Find serialized instances from context
final List<InstanceState> instanceStates = Controls.restoringInstancesJava();
// Get instances from dynamic state first
if (instanceStates != null) {
for (final InstanceState state : instanceStates) {
// Check that the instance belongs to this model
if (effectiveId.equals(state.modelEffectiveId())) {
// NOTE: Here instance must contain document
XFormsInstance.restoreInstanceFromState(this, state, INSTANCE_LOADER);
indentedLogger().logDebug("restore", "restoring instance from dynamic state", "model effective id", effectiveId, "instance effective id", state.effectiveId());
}
}
}
// Then get missing instances from static state if necessary
// This can happen if the instance is not replaced, readonly and inline
for (final Instance instance : container().getPartAnalysis().getInstances(getPrefixedId()))
if (instancesMap.get(instance.staticId()) == null)
setInlineInstance(instance);
}
public void performDefaultAction(XFormsEvent event) {
final String eventName = event.name();
if (XFormsEvents.XFORMS_MODEL_CONSTRUCT.equals(eventName)) {
// 4.2.1 The xforms-model-construct Event
// Bubbles: Yes / Cancelable: No / Context Info: None
final XFormsModelConstructEvent modelConstructEvent = (XFormsModelConstructEvent) event;
doModelConstruct(modelConstructEvent.rrr());
} else if (XFormsEvents.XXFORMS_READY.equals(eventName)) {
// This is called after xforms-ready events have been dispatched to all models
doAfterReady();
} else if (XFormsEvents.XFORMS_MODEL_CONSTRUCT_DONE.equals(eventName)) {
// 4.2.2 The xforms-model-construct-done Event
// Bubbles: Yes / Cancelable: No / Context Info: None
// TODO: implicit lazy instance construction
} else if (XFormsEvents.XFORMS_REBUILD.equals(eventName)) {
// 4.3.7 The xforms-rebuild Event
// Bubbles: Yes / Cancelable: Yes / Context Info: None
doRebuild();
} else if (XFormsEvents.XFORMS_MODEL_DESTRUCT.equals(eventName)) {
containingDocument().getXPathDependencies().modelDestruct(this);
} else if (XFormsEvents.XFORMS_RECALCULATE.equals(eventName)) {
// 4.3.6 The xforms-recalculate Event
// Bubbles: Yes / Cancelable: Yes / Context Info: None
// Recalculate and revalidate are unified
// See https://github.com/orbeon/orbeon-forms/issues/1650
doRecalculateRevalidate();
} else if (XFormsEvents.XFORMS_REVALIDATE.equals(eventName)) {
// 4.3.5 The xforms-revalidate Event
// Bubbles: Yes / Cancelable: Yes / Context Info: None
// Recalculate and revalidate are unified
// See https://github.com/orbeon/orbeon-forms/issues/1650
doRecalculateRevalidate();
} else if (XFormsEvents.XFORMS_REFRESH.equals(eventName)) {
// 4.3.4 The xforms-refresh Event
// Bubbles: Yes / Cancelable: Yes / Context Info: None
doRefresh();
} else if (XFormsEvents.XFORMS_RESET.equals(eventName)) {
// 4.3.8 The xforms-reset Event
// Bubbles: Yes / Cancelable: Yes / Context Info: None
doReset();
} else if (XFormsEvents.XFORMS_LINK_EXCEPTION.equals(eventName)) {
// 4.5.2 The xforms-link-exception Event
// Bubbles: Yes / Cancelable: No / Context Info: The URI that failed to load (xsd:anyURI)
// The default action for this event results in the following: Fatal error.
final XFormsLinkExceptionEvent exceptionEvent = (XFormsLinkExceptionEvent) event;
final Throwable throwable = exceptionEvent.throwable();
if (throwable instanceof RuntimeException)
throw (RuntimeException) throwable;
else
throw new ValidationException("Received fatal error event: " + eventName, throwable, getLocationData());
} else if (XFormsEvents.XXFORMS_XPATH_ERROR.equals(eventName)) {
// Custom event for XPath errors
// NOTE: We don't like this event very much as it is dispatched in the middle of rebuild/recalculate/revalidate,
// and event handlers for this have to be careful. It might be better to dispatch it *after* RRR.
final XXFormsXPathErrorEvent ev = (XXFormsXPathErrorEvent) event;
XFormsError.handleNonFatalXPathError(container(), ev.throwable());
} else if (XFormsEvents.XXFORMS_BINDING_ERROR.equals(eventName)) {
// Custom event for binding errors
// NOTE: We don't like this event very much as it is dispatched in the middle of rebuild/recalculate/revalidate,
// and event handlers for this have to be careful. It might be better to dispatch it *after* RRR.
final XXFormsBindingErrorEvent ev = (XXFormsBindingErrorEvent) event;
XFormsError.handleNonFatalSetvalueError(this, ev.locationData(), ev.reason());
} else if (XFormsEvents.XXFORMS_ACTION_ERROR.equals(eventName)) {
final XXFormsActionErrorEvent ev = (XXFormsActionErrorEvent) event;
XFormsError.handleNonFatalActionError(this, ev.throwable());
}
}
private void doReset() {
// TODO
// "The instance data is reset to the tree structure and values it had immediately
// after having processed the xforms-ready event."
// "Then, the events xforms-rebuild, xforms-recalculate, xforms-revalidate and
// xforms-refresh are dispatched to the model element in sequence."
Dispatch.dispatchEvent(new XFormsRebuildEvent(XFormsModel.this));
Dispatch.dispatchEvent(new XFormsRecalculateEvent(XFormsModel.this));
Dispatch.dispatchEvent(new XFormsRefreshEvent(XFormsModel.this));
}
private void doAfterReady() {
}
private void doModelConstruct(boolean rrr) {
final Element modelElement = staticModel.element();
// 1. The XML Schemas, if any, are loaded
try {
schemaValidator();
} catch (Exception e) {
final String schemaAttribute = modelElement.attributeValue(XFormsConstants.SCHEMA_QNAME);
Dispatch.dispatchEvent(new XFormsLinkExceptionEvent(XFormsModel.this, schemaAttribute, e));
}
// 2. For each instance element, an XPath data model is constructed
{
int instancePosition = 0;
for (final Instance instance : staticModel.instancesMap().values()) {
// Skip processing in case somebody has already set this particular instance
// FIXME: can this ever happen?
if (instances.get(instancePosition++) == null) {
// Load instance. This might throw an exception event (and therefore a Java exception) in case of fatal problem.
loadInitialInstance(instance);
}
}
deferredActionContext().jMarkStructuralChange();
}
// Custom event after instances are ready
Dispatch.dispatchEvent(new XXFormsInstancesReadyEvent(XFormsModel.this));
// 3. A rebuild, recalculate, and revalidate are then performed in sequence for this mode
if (rrr) {
doRebuild();
doRecalculateRevalidate();
}
}
private void loadInitialInstance(Instance instance) {
indentedLogger().startHandleOperation("load", "loading instance", "instance id", instance.staticId());
{
if (instance.useExternalContent()) {
// Load from @src or @resource
loadInitialExternalInstanceFromCacheIfNeeded(instance);
} else if (instance.useInlineContent()) {
// Load from inline content
try {
setInlineInstance(instance);
} catch (Exception e) {
final LocationData extendedLocationData = new ExtendedLocationData(instance.locationData(), "processing XForms instance", instance.element());
final Throwable throwable = new ValidationException("Error extracting or setting inline instance", extendedLocationData);
Dispatch.dispatchEvent(new XFormsLinkExceptionEvent(XFormsModel.this, null, throwable));
}
} else {
// Everything missing
final LocationData extendedLocationData = new ExtendedLocationData(instance.locationData(), "processing XForms instance", instance.element());
final Throwable throwable = new ValidationException("Required @src attribute, @resource attribute, or inline content for instance: " + instance.staticId(), extendedLocationData);
Dispatch.dispatchEvent(new XFormsLinkExceptionEvent(XFormsModel.this, "", throwable));
}
}
indentedLogger().endHandleOperation();
}
private void setInlineInstance(Instance instance) {
// Extract document
final DocumentInfo instanceDocument = instance.inlineContent();
// Set instance and associated information if everything went well
// NOTE: No XInclude supported to read instances with @src for now
indexInstance(XFormsInstance.apply(this, instance, instanceDocument));
}
private String resolveInstanceURL(Instance instance) {
return XFormsUtils.resolveServiceURL(
containingDocument(),
instance.element(),
instance.instanceSource().get(),
ExternalContext.Response.REWRITE_MODE_ABSOLUTE);
}
private void loadInitialExternalInstanceFromCacheIfNeeded(Instance instance) {
final String instanceResource = instance.instanceSource().get();
try {
if (instance.cache() && ! ProcessorImpl.isProcessorInputScheme(instanceResource)) {
// Instance 1) has cache hint and 2) is not input:*, so it can be cached
// NOTE: We don't allow sharing for input:* URLs as the data will likely differ per request
// TODO: This doesn't handle optimized submissions.
// NOTE: No XInclude supported to read instances with @src for now
final InstanceCaching caching = InstanceCaching.fromInstance(instance, resolveInstanceURL(instance), null);
final DocumentInfo documentInfo =
XFormsServerSharedInstancesCache.findContentOrLoad(
instance,
caching,
instance.readonly(),
INSTANCE_LOADER,
indentedLogger());
indexInstance(
new XFormsInstance(
this,
instance,
Option.apply(caching),
documentInfo,
instance.readonly(),
false,
true));
} else {
// Instance cannot be cached
// NOTE: Optimizing with include() for servlets has limitations, in particular
// the proper split between servlet path and path info is not done.
// TODO: Temporary. Use XFormsModelSubmission to load instances instead
if (!NetUtils.urlHasProtocol(instanceResource) && containingDocument().getContainerType().equals("portlet"))
throw new UnsupportedOperationException("<xf:instance src=\"\"> with relative path within a portlet");
// Use full resolved resource URL
// - absolute URL, e.g. http://example.org/instance.xml
// - absolute path relative to server root, e.g. /orbeon/foo/bar/instance.xml
loadNonCachedExternalInstance(instance);
}
} catch (Exception e) {
final ValidationException validationException
= OrbeonLocationException.wrapException(e, new ExtendedLocationData(instance.locationData(), "reading external instance", instance.element()));
Dispatch.dispatchEvent(new XFormsLinkExceptionEvent(XFormsModel.this, instanceResource, validationException));
}
}
private final InstanceLoader INSTANCE_LOADER = new InstanceLoader();
private class InstanceLoader implements XFormsServerSharedInstancesCache.Loader {
public DocumentInfo load(String resolvedURL, boolean handleXInclude) {
return SubmissionUtils.readTinyTree(XFormsModel.this, resolvedURL, handleXInclude);
}
}
/*
* Load an external instance using an absolute URL.
*/
private void loadNonCachedExternalInstance(final Instance instance) {
final String absoluteURLString = resolveInstanceURL(instance);
// Connect using external protocol
final Object instanceDocument;// Document or DocumentInfo
if (containingDocument().getURIResolver() == null) {
// Connect directly if there is no resolver or if the instance is globally cached
// NOTE: If there is no resolver, URLs of the form input:* are not allowed
assert ! ProcessorImpl.isProcessorInputScheme(absoluteURLString);
if (indentedLogger().isDebugEnabled())
indentedLogger().logDebug("load", "getting document from URI", "URI", absoluteURLString);
final URI absoluteResolvedURL;
try {
absoluteResolvedURL = new URI(absoluteURLString);
} catch (URISyntaxException e) {
throw new OXFException(e);
}
final scala.collection.immutable.Map<String, scala.collection.immutable.List<String>> headers =
Connection.jBuildConnectionHeadersCapitalizedIfNeeded(
absoluteResolvedURL.getScheme(),
instance.credentialsOrNull() != null,
null,
Connection.jHeadersToForward(),
containingDocument().headersGetter(),
indentedLogger()
);
final ConnectionResult connectionResult = Connection.jApply(
"GET", absoluteResolvedURL, instance.credentialsOrNull(), null,
headers, true, BaseSubmission.isLogBody(), indentedLogger()).connect(true);
instanceDocument =
ConnectionResult.withSuccessConnection(connectionResult, true, new Function1Adapter<InputStream, Object>() {
public Object apply(InputStream is) {
// TODO: Handle validating and XInclude!
// Read result as XML
// TODO: use submission code?
if (! instance.readonly())
return TransformerUtils.readDom4j(is, connectionResult.url(), false, true);
else
return TransformerUtils.readTinyTree(XPath.GlobalConfiguration(), is, connectionResult.url(), false, true);
}
});
} else {
// Optimized case that uses the provided resolver
if (indentedLogger().isDebugEnabled())
indentedLogger().logDebug("load", "getting document from resolver", "URI", absoluteURLString);
// TODO: Handle validating and handleXInclude!
if (!instance.readonly()) {
instanceDocument = containingDocument().getURIResolver().readAsDom4j(
absoluteURLString, instance.credentialsOrNull());
} else {
instanceDocument = containingDocument().getURIResolver().readAsTinyTree(XPath.GlobalConfiguration(),
absoluteURLString, instance.credentialsOrNull());
}
}
// Set instance and associated information if everything went well
// NOTE: No XInclude supported to read instances with @src for now
final DocumentInfo documentInfo = XFormsInstance.createDocumentInfoJava(instanceDocument, instance.exposeXPathTypes());
indexInstance(XFormsInstance.apply(this, instance, documentInfo));
}
public void performTargetAction(XFormsEvent event) {
// NOP
}
public void markValueChange(NodeInfo nodeInfo, boolean isCalculate) {
// Set the flags
deferredActionContext().markValueChange(isCalculate);
// Notify dependencies of the change
if (nodeInfo != null)
containingDocument().getXPathDependencies().markValueChanged(this, nodeInfo);
}
// public void markMipChange(NodeInfo nodeInfo) {
// // Notify dependencies of the change
// if (nodeInfo != null)
// containingDocument.getXPathDependencies().markMipChanged(this, nodeInfo);
// }
public void startOutermostActionHandler() {
// NOP now that deferredActionContext is always created
}
public void rebuildRecalculateRevalidateIfNeeded() {
// Process deferred behavior
final DeferredActionContext currentDeferredActionContext = deferredActionContext();
// NOTE: We used to clear deferredActionContext , but this caused events to be dispatched in a different
// order. So we are now leaving the flag as is, and waiting until they clear themselves.
if (currentDeferredActionContext.rebuild()) {
containingDocument().startOutermostActionHandler();
Dispatch.dispatchEvent(new XFormsRebuildEvent(this));
containingDocument().endOutermostActionHandler();
}
if (currentDeferredActionContext.recalculateRevalidate()) {
containingDocument().startOutermostActionHandler();
Dispatch.dispatchEvent(new XFormsRecalculateEvent(this));
containingDocument().endOutermostActionHandler();
}
}
public XFormsEventObserver parentEventObserver() {
// There is no point for events to propagate beyond the model
// NOTE: This could change in the future once models are more integrated in the components hierarchy
return null;
}
public BindingContext getDefaultEvaluationContext() {
return defaultEvaluationContext;
}
public Map<String, ValueRepresentation> getTopLevelVariables() {
return topLevelVariables;
}
public void setTopLevelVariables(Map<String, ValueRepresentation> topLevelVariables) {
this.topLevelVariables = topLevelVariables;
}
// Don't allow any external events
public boolean allowExternalEvent(String eventName) {
return false;
}
}