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

com.adobe.xfa.form.FormModel Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright 2005 Adobe Systems Incorporated All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property of
 * Adobe Systems Incorporated and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Adobe Systems
 * Incorporated and its suppliers and may be covered by U.S. and Foreign
 * Patents, patents in process, and are protected by trade secret or copyright
 * law. Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained from
 * Adobe Systems Incorporated.
 */

// Adobe Patent or Adobe Patent Pending Invention Included Within this File

package com.adobe.xfa.form;

import com.adobe.xfa.AppModel;
import com.adobe.xfa.ArrayNodeList;
import com.adobe.xfa.Attribute;
import com.adobe.xfa.ChildReln;
import com.adobe.xfa.EventPseudoModel.EventInfo;
import com.adobe.xfa.content.Content;
import com.adobe.xfa.content.ExDataValue;
import com.adobe.xfa.content.TextValue;
import com.adobe.xfa.Chars;
import com.adobe.xfa.Comment;
import com.adobe.xfa.Delta;
import com.adobe.xfa.Dispatcher;
import com.adobe.xfa.Document;
import com.adobe.xfa.DOMSaveOptions;
import com.adobe.xfa.Element;
import com.adobe.xfa.EnumAttr;
import com.adobe.xfa.EnumValue;
import com.adobe.xfa.EventManager;
import com.adobe.xfa.EventPseudoModel;
import com.adobe.xfa.Generator;
import com.adobe.xfa.HostPseudoModel;
import com.adobe.xfa.Int;
import com.adobe.xfa.LogMessage;
import com.adobe.xfa.LogMessenger;
import com.adobe.xfa.Model;
import com.adobe.xfa.ModelPeer;
import com.adobe.xfa.Node;
import com.adobe.xfa.NodeList;
import com.adobe.xfa.Obj;
import com.adobe.xfa.Packet;
import com.adobe.xfa.ProcessingInstruction;
import com.adobe.xfa.ProtoableNode;
import com.adobe.xfa.RichTextNode;
import com.adobe.xfa.Schema;
import com.adobe.xfa.ScriptHandler;
import com.adobe.xfa.ScriptTable;
import com.adobe.xfa.service.storage.XMLStorage;
import com.adobe.xfa.SOMParser;
import com.adobe.xfa.StringAttr;
import com.adobe.xfa.STRS;
import com.adobe.xfa.TextNode;
import com.adobe.xfa.XFA;
import com.adobe.xfa.XFAList;
import com.adobe.xfa.XMLMultiSelectNode;
import com.adobe.xfa.data.DataModel;
import com.adobe.xfa.data.DataNode;
import com.adobe.xfa.data.DataWindow;
import com.adobe.xfa.template.Items;
import com.adobe.xfa.template.TemplateModel;
import com.adobe.xfa.template.Value;
import com.adobe.xfa.template.containers.Container;
import com.adobe.xfa.template.containers.Draw;
import com.adobe.xfa.template.containers.ExclGroup;
import com.adobe.xfa.template.containers.Field;
import com.adobe.xfa.template.containers.PageArea;
import com.adobe.xfa.template.containers.PageSet;
import com.adobe.xfa.template.containers.Subform;
import com.adobe.xfa.template.containers.SubformSet;
import com.adobe.xfa.ut.Assertions;
import com.adobe.xfa.ut.Base64;
import com.adobe.xfa.ut.BooleanHolder;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.IntegerHolder;
import com.adobe.xfa.ut.LcData;
import com.adobe.xfa.ut.MsgFormat;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.ObjectHolder;
import com.adobe.xfa.ut.Peer;
import com.adobe.xfa.ut.PictureFmt;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.StringHolder;
import com.adobe.xfa.ut.StringUtils;
import com.adobe.xfa.ut.trace.TraceHandler.TimingType;
import com.adobe.xfa.ut.trace.Trace;
import com.adobe.xfa.ut.trace.TraceTimer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;


/**
 * A class to represent the result of joining a template and data.
 */
public class FormModel extends Model {
	
	/**
	 * @exclude from published api.
	 */
	public interface ConnectHandler {
		
		public boolean handleConnect(
				Node node, 
				String sConnectionRootRef, 
				String sConnectRef, 
				String sConnectPicture,
				Object handlerData,
				ObjectHolder ioRecursingData);
	}
		
	private static final ConnectHandler mConnectExportHandler = new ConnectHandler() {
	
		// Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc
		public boolean handleConnect(Node formNode, String sConnectionRootRef, String sConnectRef, String sConnectPicture, Object handlerData, ObjectHolder ioRecursingData) {
			Model formModel = formNode.getModel();
			assert(formModel != null);
			AppModel appModel = (AppModel)formModel.getXFAParent(); 
	
			DataNode dataNode = null;
	
			DataNode connectionDataNode = (DataNode)appModel.resolveNode(sConnectionRootRef, false, false, true);
			assert(connectionDataNode != null);
	
			DataNode parentNode = ioRecursingData.value;
	
			NodeList nodes;
			if (parentNode != null) {
				nodes = parentNode.resolveNodes(sConnectRef, true, false, true);
			}
			else {
				nodes = connectionDataNode.resolveNodes(sConnectRef, true, false, true);
			}
	
			boolean bMultiple = isSomMultiple(sConnectRef);
	
			int nLen = nodes.length();
			for (int i = 0; i < nLen; i++) {
				Node resolvedNode = (Node)nodes.item(i);
	
				// did we already use the node?
				if (bMultiple && resolvedNode.isMapped()) {
					continue;
				}
				else if (dataNode == null) {
					dataNode = (DataNode)resolvedNode;    // did we use it?
					break;
				}
			}
	
			// do we need to create one ?
			if (dataNode == null) {
				DataModel dataModel = DataModel.getDataModel(appModel, false, false);
				
				boolean bUseDV = useDV(formNode);
				if (parentNode != null) {
					// use the parent node as the context
					dataNode = (DataNode)dataModel.resolveRef(sConnectRef, parentNode, bUseDV, false);
				}
				else
					dataNode = (DataNode)dataModel.resolveRef(sConnectRef, connectionDataNode, bUseDV, false);
			}
	
			if (dataNode != null) {
				if (dataNode.getIsDDPlaceholder()) {
					// clear place holder flag when the node is used
					dataNode.setIsDDPlaceholder(false);
				}
	
				if (formNode instanceof FormField) {
					((FormField)formNode).setDataNode(dataNode, true, false, sConnectPicture, true);
					((FormModel)formModel).consumeDataNode(null, dataNode, FormModel.DatasetSelector.MAIN_DATASET);
				}
				else if (formNode instanceof FormExclGroup) {
					((FormExclGroup)formNode).setDataNode(dataNode, true, false);
					((FormModel)formModel).consumeDataNode(null, dataNode, FormModel.DatasetSelector.MAIN_DATASET);
				}
				else if (formNode instanceof FormSubform) {
					// watson bug 1525961 marked the node as mapped so we don't use it again.  
					((FormModel)formModel).consumeDataNode(null, dataNode, FormModel.DatasetSelector.MAIN_DATASET);
					ioRecursingData.value = dataNode;  // set the data node as the new parent
    			}
			}	
			
			return true; // always continue recursing
		}
	};
	
	/**
	 * Support for connectionSet and connectionData
	 */
	private static final ConnectHandler mConnectImportHandler = new ConnectHandler() {
		
		// Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc
		public boolean handleConnect(Node formNode, String sConnectionRootRef, String sConnectRef, String sConnectPicture, Object handlerData, ObjectHolder ioRecursingData) {
			
			FormModel formModel = (FormModel)formNode.getModel();
			assert(formModel != null);

			AppModel appModel = formNode.getModel().getAppModel(); 
	
			DataNode parentNode = ioRecursingData.value;
	
			// refs can be absolute or relative
			String sDataRef = sConnectRef;
			boolean bMultiple = isSomMultiple(sDataRef);
	
			NodeList resolvedDataNodes;			
			if (parentNode != null)
				resolvedDataNodes = parentNode.resolveNodes(sDataRef, true, false, true);
			else
				resolvedDataNodes = appModel.resolveNodes(sDataRef, true, false, true);
	
			if (resolvedDataNodes.length() != 0) {
				
				DataNode dataNode = null;
				for (int i = 0; i < resolvedDataNodes.length(); i++) {
					DataNode item = (DataNode)resolvedDataNodes.item(i);
					
					// if there are more than one in the list - happy dynamic data!!
					if (bMultiple && item.isMapped())
						continue;
					
					dataNode = item;
					break;
				}
	
				if (dataNode != null) {
					if (formNode instanceof FormField) {
						((FormField)formNode).setDataNode(dataNode, false, false, sConnectPicture, false/*CL#708930*/);
						formModel.consumeDataNode(null, dataNode, FormModel.DatasetSelector.MAIN_DATASET);
					}
					else if (formNode instanceof FormExclGroup) {
						((FormExclGroup)formNode).setDataNode(dataNode, false, false);
						formModel.consumeDataNode(null, dataNode, FormModel.DatasetSelector.MAIN_DATASET);
					}
					else if (formNode instanceof FormSubform) {
						// sets this to be the new parent if we are at a subform - equivalent to the poConnectionDataParent in createAndMatchNode
						formModel.consumeDataNode(null, dataNode, FormModel.DatasetSelector.MAIN_DATASET);
						ioRecursingData.value = dataNode;
					}
	
					if (formNode instanceof Container) {
						((FormModel)formNode.getModel()).setConnectionDataContextInfo((Container)formNode, dataNode);
					}
				}
			}
	
			return true; // always continue recursing
		}
	};
	
	private static final ConnectHandler mConnectImportPermCheckHandler = new ConnectHandler() {
		
		/*	Watson bug 1368015 do a first pass to see if this will violate XFA perms.*/
		public boolean handleConnect(Node formNode, String sConnectionRootRef, String sConnectRef, String sConnectPicture, Object handlerData, ObjectHolder ioRecursingData) {
			assert handlerData != null;
			
			BooleanHolder overallResult = (BooleanHolder)handlerData;
			AppModel appModel = formNode.getModel().getAppModel();
			
			Node parentNode = ioRecursingData.value;

			// refs can be absolute or relative
			String sDataRef = sConnectRef;
			boolean bMultiple = isSomMultiple(sDataRef);

			NodeList resolvedDataNodes;			
			if (parentNode != null)
				resolvedDataNodes = parentNode.resolveNodes(sDataRef, true, false, true);
			else
				resolvedDataNodes = appModel.resolveNodes(sDataRef, true, false, true);

			if (resolvedDataNodes.length() != 0) {
				
				DataNode dataNode = null;
				for (int i = 0; i < resolvedDataNodes.length(); i++) {
					DataNode item = (DataNode)resolvedDataNodes.item(i);
					
					// if there are more than one in the list - happy dynamic data!!
					if (bMultiple && item.isMapped())
						continue;
					
					dataNode = item;
					break;
				}
				
				if (dataNode != null) {
					
					// update the parent for the next call
					if (formNode instanceof FormSubform)
						ioRecursingData.value = dataNode;
					
					// check perms
					if (!formNode.checkPerms() || !formNode.checkAncestorPerms())
						overallResult.value = false;
				}
			}

			return overallResult.value; 
		}
	};
	
	/**
	 * @exclude from published api.
	 */
	public abstract static class Execute {

		public abstract Object clone();

		public abstract void execute(String sConnection, 
							 int	 meRunAt,
							 int	 meExecuteType);
	}
	
	/**
	 * Provides a mechanism for FormModel to execute an event on a server.
	 * 
	 * @exclude from published api.
	 */
	public abstract static class ServerExchange {
		
		/**
		 * Allows this implementation to react to the changed data after the
		 * FormModel has loaded the data from the response. This is called as
		 * the last step after data has been successfully exchanged with the
		 * server.
		 * 
		 * The default implementation does nothing.
		 */
		public void remerge() {}

		/**
		 * Sends a request to the server and returns the server's response. The
		 * derived class must implement the transfer mechanism. The contents in
		 * each direction are formatted as UTF-8 encoded XDP data. The request
		 * includes the DataModel contents, as well as an execEvent packet (with
		 * context and activity attributes) that describe the event to be
		 * executed on the server.
		 * 
		 * @param request
		 *            a buffer containing the data to be sent in the server
		 *            request.
		 * @return a buffer containing the server response, this is expected to
		 *         be UTF-8 encoded XDP data.
		 */
		public abstract byte[] sendToServer(byte[] request);
	}
	
	/**
	 * @exclude from published api.
	 */
	public abstract static class Submit {
		
		/**
		 * @exclude from published api.
		 */
		public static class SubmitParams  {
			
			@FindBugsSuppress(pattern="EI_EXPOSE_REP,EI_EXPOSE_REP2")
			private final String[] 	mPackets;
			private final String 	msSubmitUrl;
			private final int 		meFormat;
			private final boolean 	mbEmbedPDF;
			
			private 	  String 	msTextEncoding;

			// secure delivery
			private String msCertificate;
			private List mSignDispatchers;
			
			public SubmitParams(String[] packets, 
						 String sSubmitUrl, 
						 int eFormat, 				
						 String sTextEncoding,
						 boolean bEmbedPDF) {
				mPackets = packets;
				msSubmitUrl = sSubmitUrl;
				meFormat = eFormat;
				msTextEncoding = sTextEncoding;
				mbEmbedPDF = bEmbedPDF;
			}
			public String getCertificate() { return msCertificate; }

			public boolean getEmbedPDF() { return mbEmbedPDF; }
			public int getFormat() { return meFormat; }

		
			public String[] getPackets() { return mPackets; }
			public List getSignDispatchers() { return mSignDispatchers; }
			public String getSubmitUrl() { return msSubmitUrl; }
			public String getTextEncoding() { return msTextEncoding; }
			public void setCertificate(String sCertificate) { msCertificate = sCertificate; }

			public void setSignDispatchers(List poSD) { mSignDispatchers = poSD; }
			public void setTextEncoding(String sTextEncoding) { msTextEncoding = sTextEncoding; }
		}

		public abstract Object clone();

		public abstract void submit(SubmitParams params);
		
		//bug 2426847v		
		public abstract void setPacketToIgnore(Node pPacket);
	}

	/**
	 * A base class that an implementation can derive from to
	 * interact with the form validation process. Before performing each
	 * validation test, the validation framework calls back to a Validate
	 * instance to determine whether that kind of validation test is enabled.
	 * Each time a validation failure occurs, the validation framework calls
	 * back to a Validate instance to record the failure.
	 * 

* This default implementation has all kinds of validations enabled by * default. It takes no action on validation failures, except to increment * the number of failures. */ public static class Validate { private boolean mbScriptTestEnabled; // Should script validation be performed? private boolean mbNullTestEnabled; // Should nullTest validation be performed? private boolean mbFormatTestEnabled; // Should formatTest validation be performed? private boolean mbBarcodeTestEnabled; // Should barcodeTest validation be performed? /** * @exclude from published api. */ protected int mnNumFailures; /** * Initializes a newly created Validate object so that * all kinds validation tests are enabled. */ public Validate() { mbScriptTestEnabled = true; mbNullTestEnabled = true; mbFormatTestEnabled = true; mbBarcodeTestEnabled = true; } /** * Initializes a newly created Validate object so that * specified validation tests are enabled. * @param bScriptTestEnabled determines whether script validations are enabled * @param bNullTestEnabled determines whether null validations are enabled * @param bFormatTestEnabled determines whether format validations are enabled * @param bBarcodeTestEnabled determines whether barcode validations are enabled */ public Validate(boolean bScriptTestEnabled, boolean bNullTestEnabled, boolean bFormatTestEnabled, boolean bBarcodeTestEnabled) { mbScriptTestEnabled = bScriptTestEnabled; mbNullTestEnabled = bNullTestEnabled; mbFormatTestEnabled = bFormatTestEnabled; mbBarcodeTestEnabled = bBarcodeTestEnabled; } /** * Is called when a validation pass is started. */ public void onValidateStart() { } /** * Is called when a validation pass ends. */ public void onValidateEnd() { } /** * Creates a copy of this Validate. * The set of tests enabled is copied, and the number of failures is set to zero. */ public Validate clone() { return new Validate(mbScriptTestEnabled, mbNullTestEnabled, mbFormatTestEnabled, mbBarcodeTestEnabled); } /** * Determines if barcode validation tests are enabled. * * @return true if barcode validation tests are enabled; * otherwise false */ public boolean isBarcodeTestEnabled() { return mbBarcodeTestEnabled; } /** * Sets whether barcode validation tests are to be enabled. * * @param bEnabled * true if barcode tests are to be performed. */ public void setBarcodeTestEnabled(boolean bEnabled) { mbBarcodeTestEnabled = bEnabled; } /** * Determines if format validation tests are enabled. * * @return true if format validation tests are enabled; * otherwise false */ public boolean isFormatTestEnabled() { return mbFormatTestEnabled; } /** * Sets whether format validation tests are to be enabled. * * @param bEnabled * true if format tests are to be performed. */ public void setFormatTestEnabled(boolean bEnabled) { mbFormatTestEnabled = bEnabled; } /** * Determines if null validation tests are enabled. * * @return true if null validation tests are enabled; * otherwise false */ public boolean isNullTestEnabled() { return mbNullTestEnabled; } /** * Sets whether null validation tests are to be enabled. * * @param bEnabled * true if null tests are to be performed. */ public void setNullTestEnabled(boolean bEnabled) { mbNullTestEnabled = bEnabled; } /** * Determines if script validation tests are enabled. * * @return true if script validation tests are enabled; * otherwise false */ public boolean isScriptTestEnabled() { return mbScriptTestEnabled; } /** * Sets whether script validation tests are to be enabled. * * @param bEnabled * true if script tests are to be performed. */ public void setScriptTestEnabled(boolean bEnabled) { mbScriptTestEnabled = bEnabled; } /** * Is called by the validation framework when a barcode validation test * fails. This base implementation increments the number of failures and * returns true. * * @param field * the field containing the barcode test * @param sValidationMessage * the validation error message * @return true if validation should continue */ public boolean onValidateBarcodeTestFailed( FormField field, String sValidationMessage) { mnNumFailures++; return true; } /** * Is called by the validation framework when a format validation test * fails. This base implementation increments the number of failures and * returns true. * * @param field * the field containing the format test * @param sValidationMessage * the validation error message * @param bDisableValidate * indicates whether future validations for this field should be disabled * @return true if validation should continue */ public boolean onValidateFormatTestFailed( FormField field, String sValidationMessage, BooleanHolder bDisableValidate) { mnNumFailures++; return true; } /** * Is called by the validation framework when a null validation test fails. * This base implementation increments the number of failures and * returns true. * * @param node * the node containing the null test * @param sValidationMessage * the validation error message * @param bDisableValidate * indicates whether future validations for this ProtoableNode should be disabled * @return true if validation should continue */ public boolean onValidateNullTestFailed( ProtoableNode node, String sValidationMessage, BooleanHolder bDisableValidate) { mnNumFailures++; return true; } /** * Is called by the validation framework when a script validation test * fails. This base implementation increments the number of failures and * returns true. * * @param node * the node containing the script test * @param sScript * the text of the validation script that failed * @param sLanguage * the language of the script * @param sValidationMessage * the validation error message * @param bDisableValidate * indicates whether future validations for this ProtoableNode should be disabled * @return true if validation should continue */ public boolean onValidateScriptFailed( ProtoableNode node, String sScript, String sLanguage, String sValidationMessage, BooleanHolder bDisableValidate) { mnNumFailures++; return true; } /** * Returns the number of validation failures since the fail count was * last reset. * * @return the number of validation failures */ public int getFailCount() { return mnNumFailures; } /** * Sets the number of validation failures to zero. The validation * framework calls this method before it starts each validation pass * initiated by: *

  • a call to an execValidate method
  • *
  • an execute or submit action of an event, or
  • *
  • an excit event of a FormSubform or FormExclGroup.
  • *

    * This method is not called when * {@link FormModel#recalculate(boolean, FormModel.Validate, boolean)} * is called. */ public void resetFailCount() { mnNumFailures = 0; } /** * Validates a barcode. The default implementation always returns * true. * * @param element * the barcode form Element. * @param sBarcodeType * a String that identifies the barcode pattern * @param sValue * the barcode value * @return true if the barcode is valid */ public boolean validateBarcode( Element element, String sBarcodeType, String sValue) { return true; } } private static class ExecuteInfo { public final String msEventContext; //public final String msConnection; //public final int meRunAt; // = EnumAttr.RUNAT_BOTH; //public final int meExecuteType; // = EnumAttr.EXECUTETYPE_IMPORT; public final ProtoableNode mExecuteContextNode; public ExecuteInfo( String sEventContext, //String sConnection, int eRunAt, int eExecuteType, ProtoableNode executeContextNode) { msEventContext = sEventContext; //msConnection = sConnection; //meRunAt = eRunAt; //meExecuteType = eExecuteType; mExecuteContextNode = executeContextNode; } } private static class ScriptInfo { public final String msScript; public final String msScriptLanguage; public final String msEventContext; public final int meRunAt; // = EnumAttr.RUNAT_BOTH; public final ProtoableNode mScriptContextNode; public String msTarget = null; public ScriptInfo(String sScript, String sScriptLanguage, String sEventContext, int eRunAt, ProtoableNode scriptContextNode) { msScript = sScript; msScriptLanguage = sScriptLanguage; msEventContext = sEventContext; meRunAt = eRunAt; mScriptContextNode = scriptContextNode; } } private static class SubmitInfo { // public final String msTarget; // public final String msFormat; // public final String msTextEncoding; // public final String msXDPContent; // public final boolean mbEmbedPDF; public final String msEventContext; public final ProtoableNode mSubmitContextNode; public SubmitInfo( //String sTarget, String sFormat, String sTextEncoding, String sXDPContent, boolean bEmbedPDF, String sEventContext, ProtoableNode submitContextNode) { // msTarget = sTarget; // msFormat = sFormat; // msTextEncoding = sTextEncoding; // msXDPContent = sXDPContent; // mbEmbedPDF = bEmbedPDF; msEventContext = sEventContext; mSubmitContextNode = submitContextNode; } } private static class ValidateInfo { public final String msScript; public final String msScriptLanguage; // public final boolean mbNullTest; // public final boolean mbFormatTest; // public final boolean mbScriptTest; // public final boolean mbBarcodeTest; public final String msEventContext; public final String msBarcodeType; public final int meRunAt; // = EnumAttr.RUNAT_BOTH; public final ProtoableNode mScriptContextNode; public ValidateInfo( String sScript, String sScriptLanguage, //boolean bNullTest, boolean bFormatTest, boolean bScriptTest, boolean bBarcodeTest, String sEventContext, String sBarcodeType, int eRunAt, ProtoableNode scriptContextNode) { msScript = sScript; msScriptLanguage = sScriptLanguage; // mbNullTest = bNullTest; // mbFormatTest = bFormatTest; // mbScriptTest = bScriptTest; // mbBarcodeTest = bBarcodeTest; msEventContext = sEventContext; msBarcodeType = sBarcodeType; meRunAt = eRunAt; mScriptContextNode = scriptContextNode; } } private static class LayoutContentInfo { public final Element mNode; public boolean mbInitializeOccurred; // Convenience constructor public LayoutContentInfo(Element node) { mNode = node; //mbInitializeOccurred = false; } } private final static FormSchema gsFormSchema = new FormSchema(); // masks for events /** @exclude from published api */ final static int XFAEVENTTYPE_EVENTS = 1; /** @exclude from published api */ final static int XFAEVENTTYPE_CALCULATE = 2; /** @exclude from published api */ final static int XFAEVENTTYPE_VALIDATE = 4; /** @exclude from published api */ final static int XFAEVENTTYPE_ALL = 7; /** * if a script executes this many times in one recalculation, then a cyclic * dependency is assumed to exist. */ private static final int CYCLE_MAX = 10; private static final Boolean UPDATE_DATA = true; /** * @exclude from published api. */ public enum DatasetSelector { MAIN_DATASET, ALT_DATASET } // Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc private static boolean getConnectSOMStrings(Node node, String strConnectionName, int eUsage, StringHolder outConnectionRootRef, StringHolder outConnectRef, StringHolder sConnectPicture /* = null */) { Element connectNode = null; if (node instanceof Field || node instanceof ExclGroup || node instanceof Subform ) { connectNode = ((Container)node).getConnectNode(strConnectionName, eUsage, false); } if (connectNode != null) { // get the ref outConnectRef.value = connectNode.getAttribute(XFA.REFTAG).toString(); // this ref will be in the form "Body.etc..." // the actual data will reside in // "xdp.datasets.connectionData.connectionName.body.etc..." outConnectionRootRef.value = "!" + XFA.CONNECTIONDATA + "." + strConnectionName; if (sConnectPicture != null) { Element picture = connectNode.getElement(XFA.PICTURETAG, true, 0, false, false); if (picture != null) { TextNode textNode = picture.getText(true, false, false); if (textNode != null) { sConnectPicture.value = textNode.getValue(); } } } return true; } return false; } /** * Gets the DataNode from the form node. * * @exclude from published api. */ static DataNode getDataNode(Node formNode) { if (null != formNode) { if (formNode instanceof FormField) { return ((FormField)formNode).getDataNode(); } else if (formNode instanceof FormChoiceListField) { return ((FormChoiceListField)formNode).getDataNode(); } else if (formNode instanceof FormSubform) { return ((FormSubform)formNode).getDataNode(); } else if (formNode instanceof FormExclGroup) { return ((FormExclGroup)formNode).getDataNode(); } } return null; } /** * Returns the FormModel held within the AppModel. * * @param appModel * the AppModel to search * @param bCreateIfNotFound * if true, and the FormModel does not exist, * then one will be created and returned; if false * and the FormModel does not exist, null is * returned * * @return the FormModel contained within appModel; null if * not found and bCreateIfNotFound is false. */ public static FormModel getFormModel(AppModel appModel, boolean bCreateIfNotFound /* = true */) { FormModel form = null; if (appModel != null) { TemplateModel template = null; for (Node child = appModel.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (child instanceof FormModel) { form = (FormModel)child; } else if (child instanceof TemplateModel) { template = (TemplateModel)child; } } if (bCreateIfNotFound && form == null && template != null) { FormModel newFormModel = new FormModel(appModel, null); // FormModel is always in its own document newFormModel.setDocument(Document.createDocument(appModel)); newFormModel.setXmlPeer( new ModelPeer( newFormModel.getDocument(), null, STRS.XFAFORMNS_CURRENT, XFA.FORM, XFA.FORM, null, newFormModel)); appModel.notifyPeers(Peer.CHILD_ADDED, XFA.FORM, newFormModel); return getFormModel(appModel, false); } } // if app model is empty, form model will be too! return form; } /** * Gets the first ancestor that is mapped * * @param formNode * the start node * @return the first mapped parent, null if none found. * * @exclude from published api. */ static Element getMappedParent(Node formNode) { // Search up the Form DOM hierarchy for a mapped node if (formNode == null) { return null; } Element parent = formNode.getXFAParent(); if (parent == null) { return parent; } else if (parent.isMapped()) { DataNode dataNode = getDataNode(parent); if (dataNode == null) return getMappedParent(parent); else return parent; } else { return getMappedParent(parent); } } private static Schema getModelSchema() { return gsFormSchema; } /** * Gets the correct text for the given validate method. * * @exclude from published api. */ static String getValidationMessage(Element validateNode, String aType) { return TemplateModel.getValidationMessage(validateNode, aType); } /** * Recursively checks a data description to ensure that it is suitable for * incremental merge. * * @param dataDescriptionNode * the data description node to search * @return true if incremental merge should be attempted; false if the data * description supports variable occurrences of subforms, or a * "choice" or "unordered" relation is supported. */ private static boolean incrementalMergeCheckDataDescription(Node dataDescriptionNode) { if (dataDescriptionNode instanceof Element) { Element element = (Element)dataDescriptionNode; int nMinOccur = 1; int nMaxOccur = 1; int index; index = element.findAttr(STRS.DATADESCRIPTIONURI, "minOccur"); if (index != -1) { String sMinOccur = element.getAttrVal(index); if (!StringUtils.isEmpty(sMinOccur)) { try { nMinOccur = Integer.parseInt(sMinOccur); } catch (NumberFormatException ex) {} } } index = element.findAttr(STRS.DATADESCRIPTIONURI, "maxOccur"); if (index != -1) { String sMaxOccur = element.getAttrVal(index); if (!StringUtils.isEmpty(sMaxOccur)) { try { nMaxOccur = Integer.parseInt(sMaxOccur); } catch (NumberFormatException ex) {} } } // If variable occurrences are allowed, we can't do an // incremental merge. boolean bVariableOccurrence = (nMinOccur != nMaxOccur || nMinOccur == -1 || nMaxOccur == -1); if (bVariableOccurrence) return false; // Can't support relation=choice or relation=unordered index = element.findAttr(STRS.DATADESCRIPTIONURI, "model"); if (index != -1) { String sModel = element.getAttrVal(index); if (sModel.equals("choice") || sModel.equals("unordered")) return false; } // Recursively apply search to child elements for (Node child = element.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if ( ! incrementalMergeCheckDataDescription(child)) return false; } } return true; } // Adobe patent application tracking # P624, entitled "Form-based Data Storage And Retrieval", inventors: Matveief,Young,Solc /** * @exclude from published api. */ public static void recurseConnectOnNode(Node node, String strConnectionName, int eUsage, ConnectHandler handler, Object handlerData) { ObjectHolder recursingData = new ObjectHolder(); recurseConnectOnNodeHelper(node, strConnectionName, eUsage, handler, handlerData, recursingData); } // Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc private static boolean recurseConnectOnNodeHelper(Node node, String strConnectionName, int eUsage, ConnectHandler handler, Object handlerData, ObjectHolder recursingData) { // CAUTION: input node to this method is not necessarily a form node // - can be template node when called from wsdl connection set proxy StringHolder strConnectionRootRef = new StringHolder(); StringHolder strConnectRef = new StringHolder(); StringHolder strConnectPictureRef = new StringHolder(); ObjectHolder tempioRecursingData = new ObjectHolder(); boolean bContinue = true; for (Node child = node.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { strConnectionRootRef.value = null; strConnectRef.value = null; strConnectPictureRef.value = ""; tempioRecursingData.value = recursingData.value; if (child instanceof Container) { if (getConnectSOMStrings(child, strConnectionName, eUsage, strConnectionRootRef, strConnectRef, strConnectPictureRef)) { // strConnectRef will be in the form "Body.etc..." // the actual data will reside in "xdp.datasets.connectionData.connectionName.Body.etc..." // note that the handler may change the value of tempioRecursingData. // in the import case it will set it to the new parent bContinue = handler.handleConnect(child, strConnectionRootRef.value, strConnectRef.value, strConnectPictureRef.value, handlerData, tempioRecursingData); } if (bContinue && eUsage == EnumAttr.USAGE_IMPORTONLY) { // is it really a form node? if (child instanceof FormSubform || child instanceof FormField || child instanceof FormExclGroup) { // need to get 'real' form model since this is a static method ((FormModel)child.getModel()).setDynamicProperties((Container)child, strConnectionName, false); } } if (bContinue) { bContinue = recurseConnectOnNodeHelper(child, strConnectionName, eUsage, handler, handlerData, tempioRecursingData); } // is there anything to clean up now that recursion is complete Container.FormInfo formInfo = ((Container)child).getFormInfo(); if (formInfo != null) { ((Container)child).setFormInfo(null); } } if (!bContinue) break; } return bContinue; } /** @exclude from published api. */ static void setValidationMessage(Element validateNode, String sMessage, String aType) { TemplateModel.setValidationMessage(validateNode, sMessage, aType); } /** * Determines if a SOM expression returns multiple nodes. Previously just * checking for '*' was sufficient, but with predicate expressions we also * have to check for .[expr] and .(expr). * * @param sSom * the SOM expression to test * @return true if the SOM expression can return multiple nodes. * @exclude from published api. */ static boolean isSomMultiple(String sSom) { // Search for [*] expressions if (sSom.indexOf('*') != -1) return true; // Search for .[formcalc] expressions if (sSom.contains(".[")) return true; // Search for .(javascript) expressions if (sSom.contains(".(")) return true; return false; } private boolean mbWeightedData; private boolean mbAdjustData; private boolean mbEmptyMerge; // Flag to store if data needs is to be used during the merge private boolean mbMergeComplete = true; // Flag to store if the merge is done. private boolean mbAllowNewNodes; // Flag to store that the new form nodes can be created private boolean mbExchangingDataWithServer; // Flag to indicate if we are executing a script with runAt = server. // We don't execute initialize scripts in this case. private boolean mbRegisterNewEvents = true; // Flag used to indicate that we don't need to register the events // used when layout is peeking to get the overflow trailer size information private boolean mbValidateBeforeSubmit; // Flag used to indicate if we need to validate before submitting private boolean mbValidateBeforeExecute; // Flag used to indicate if we need to validate before executing private DataModel mDataModel; // The Data Model private TemplateModel mTemplateModel; // The Template Model private DataNode mStartNode; // The data root for the merge private final List mGlobalDataNodes = new ArrayList(); // list of global data nodes private final List mExplicitMatchNodes = new ArrayList(); // list of Form nodes with dataRefs or globals private int mnCalcEventId; private int mnValidateEventId; private int mnValidationStateEventId; private boolean mbRecursiveIndexChange; private boolean mbEnableIncrementalMerge = true; private boolean mbWasIncrementalMerge; private final List mPendingCalculateNodes = new ArrayList(); private int mnNextPendingCalculateNode; private final List mPendingValidateNodes = new ArrayList(); private int mnNextPendingValidateNode; private final List mNewValidateNodes = new ArrayList(); private Validate mValidate; private Validate mDefaultValidate; // Holds a list of XFAContainerImpl that we need to fire the validationState event // for as soon as validation processing is complete. private final List moValidationStateChanges = new ArrayList(); private int mnValidationRecursionDepth; private final List mLayoutContent = new ArrayList(); // list of Form nodes that have to be removed private Subform mRootSubform; // the current root subform private FormSubform mRootFormSubform; private Element mCurrentPageSet; // the current page area to append new pages to private DataNode mDataDescription; // data description root us a wrapper to ensure it doesn't go out of scope private String msLocale = ""; private boolean mbMatchDescendantsOnly = false; // used for dynamic merge in a pageArea private String msSubmitURL; private String[] mExcludeList; // a list of excluded activities private int meRunAtSetting = EnumAttr.RUNSCRIPTS_BOTH; private ServerExchange mServerExchange; private Submit mSubmit; private Execute mExecute; private HostPseudoModel mHostPseudoModel; private EventPseudoModel mEventPseudoModel; // private SignaturePseudoModel mpoSignaturePseudoModel; // JavaPort: Not ported yet private boolean mbIgnoreCalcEnabledFlag; private boolean mbIgnoreValidationsEnabledFlag; private String msConnectionName; private boolean mbConnectionMerge; private boolean mbFormStateUsage; private boolean mbFormStateRemoved; private boolean mbOverlayDataMergeUsage; private int mnPanel; private boolean mbIsXFAF; private FormField mActiveField; private FormField mPrevActiveField; private FormSubform mDeltasSubform; private boolean mbRestoreDeltas; private boolean mbForceRestore; private boolean mbIsCalculating; private boolean mbDisableRemerge = false; private boolean mbSkipCyclicAndDuplicateCheck = false; // We support two consumption modes in the merge algorithm: global consumption // (the legacy mode) which will only bind a non-global, non-single-dataRef binding // to a given node once, and local consumption, which only binds once within each // parent context. The later is needed for relational data models (among others) // where, for instance, the list of owners for several items might include some // of the same members. private boolean mbGlobalConsumption = true; /** * Defines the callback interface that will be invoked immediately after * a merge but before any initialize events are fired. * @see FormModel#setPostMergeHandler(PostMergeHandler, Object) * @see FormModel#getPostMergeHandler() */ public interface PostMergeHandler { /** * Defines the callback method that will be invoked immediately after a merge but before * any initialize events are fired. * @param clientData the Object passed to {@link FormModel#setPostMergeHandler(PostMergeHandler, Object)} */ public void handlePostMerge(Object clientData); } private PostMergeHandler mPostMergeHandler; private Object mPostMergeHandlerClientData; /** * @exclude from published api. */ public FormModel(Element parent, Node prevSibling) { super(parent, prevSibling, STRS.XFAFORMNS_CURRENT, XFA.FORM, XFA.FORM, STRS.DOLLARFORM, XFA.FORMTAG, XFA.FORM, getModelSchema()); } /** * Adds a scripting dependency between two nodes. * * @param node * the Element that is dependent on some other object for * calculation or validation * @param dependsOn * the object that node depends on * @param bIsCalculate * bIsCalculate is true if it's a calculate script, false if * validate script * * @exclude from published api. */ void addScriptDependency(Element node, Obj dependsOn, boolean bIsCalculate) { List listenerTable = null; if (node instanceof FormField) listenerTable = ((FormField)node).getFormListeners(true); else if (node instanceof FormExclGroup) listenerTable = ((FormExclGroup)node).getFormListeners(true); else if (node instanceof FormSubform) listenerTable = ((FormSubform)node).getFormListeners(true); if (listenerTable != null) { // add new dependency to the moFormListeners list, and add a listener to be // notified of changes via updateFromPeer. if (dependsOn instanceof Obj) { FormListener listener = new FormListener(this, node, dependsOn, bIsCalculate); listenerTable.add(listener); } } } /** * @exclude from published api. */ public void addUseNode(Element useNode) { if (isLoading() || // don't support protos on load mbAllowNewNodes || // mbAllowNewNodes is set to true when we are merging or cloning nodes useNode.isContainer() || mTemplateModel == null || mTemplateModel.getLegacySetting(AppModel.XFA_LEGACY_V27_SCRIPTING)) return; super.addUseNode(useNode); } /** * @exclude from published api. * @param container */ void addValidationStateChanged(Container container) { moValidationStateChanges.add(container); } /** * Not supported on FormModel * @exclude from published api. */ public void addUseHRefNode(Element useHRefNode) { } /** * Create and/or move data matches for default bindings. Only used * by the legacy merge algorithm, as createAndAdjustData() * reduces the number of tree walks by one. * * @param formParent * the form model Element start from * @param dataParent * the data model Element to start from * @exclude from published api. */ private void adjustData(Element formParent, Element dataParent) { // Search through the Form DOM starting at poFormParent looking for MATCH_ONCE form nodes which // either don't have data nodes or have data nodes which don't match the form hierarchy. for (Node formChild = formParent.getFirstXFAChild(); formChild != null; formChild = formChild.getNextXFASibling()) { Element dataChild = null; boolean bAdjustChild = true; int eMergeType = mergeType(formChild, "", null); switch (eMergeType) { case EnumAttr.MATCH_ONCE: { // We don't want to create data for leaders and trailers, but this routine should // only get called on their children. if (formChild instanceof FormSubform && ((FormSubform)formChild).isLayoutNode()) { assert(false); // JEY TODO: Actually, I don't think this ever worked becase the call // that sets isLayoutNode() is made after postMerge() is called.... break; } // get the data node if (formChild.isMapped()) dataChild = getDataNode(formChild); if (dataChild != null) { // Move data nodes which don't match the form hierarchy. Otherwise the globally // consumptive merge algorithm has a hard time round-tripping. (Not that this // entirely fixes that issue, but it helps in the simpler cases.) if (dataParent != null) { boolean bMove = false; if (dataParent != dataChild.getXFAParent()) { bMove = true; } // Re-sort children of an unordered subformSet to be in form order. else if (formParent instanceof FormSubformSet && EnumAttr.RELATION_UNORDERED == formParent.getEnum(XFA.RELATIONTAG)) { bMove = true; } if (bMove) { // Don't move data nodes which are encoded as attributes. if (dataChild.getClassTag() == XFA.DATAVALUETAG) { if (((DataNode)dataChild).isAttribute()) break; } dataParent.appendChild(dataChild, true); } } } else { // No match found, create an empty data node in the data DOM if (dataParent == null) dataChild = createDataNode((Container)formChild, mStartNode, eMergeType, false); else dataChild = createDataNode((Container)formChild, dataParent, eMergeType, false); bindNodes(formChild, (DataNode)dataChild, true); consumeDataNode(null, dataChild, FormModel.DatasetSelector.MAIN_DATASET); } break; } case EnumAttr.MATCH_DESCENDANT: case EnumAttr.MATCH_DATAREF: case EnumAttr.MATCH_GLOBAL: { // don't process child, createAndAdjustData will handle this node bAdjustChild = false; break; } case EnumAttr.MATCH_NONE: // do nothing break; } if (bAdjustChild && formChild.isContainer()) { if (dataChild == null) dataChild = dataParent; // don't process exclGroups if the data node is a data value. if (formChild instanceof ExclGroup && dataChild.getClassTag() == XFA.DATAVALUETAG) continue; // Recurse through MATCH_ONCE descendants adjustData((Element)formChild, dataChild); } } } /** * Helper function to permit the temporary update the mbAllowNewNodes setting. * This should call allowNewNodes(TRUE) before cloning any form node to avoid exceptions * @param bAllow the new value of mbAllowNewNodes * @return the old value of mbAllowNewNodes */ private boolean allowNewNodes(boolean bAllow) { boolean bOldValue = mbAllowNewNodes; mbAllowNewNodes = bAllow; return bOldValue; } /** * Sets the value of the form node from the data node. Set the * peering/mapping for the nodes. * * @param formNode * a node from the FormModel * @param dataNode * a node from the DataModel * @param bUpdateData * if true the value in dataNode should be updated * using the value from formNode * @exclude from published api. */ void bindNodes(Node formNode, DataNode dataNode, boolean bUpdateData /* = false */ ) { if (dataNode == null) return; boolean bPeer = true; if (mbConnectionMerge) { Element parentDataNode = dataNode.getXFAParent(); while (parentDataNode != null && !(parentDataNode instanceof DataModel)) { // don't set up a peer relationship if the data node is under // the connectionData dataset if (parentDataNode.getClassTag() == XFA.DATAGROUPTAG && (parentDataNode.getName() == XFA.CONNECTIONDATA)) { bPeer = false; break; } parentDataNode = parentDataNode.getXFAParent(); } } if (bUpdateData) { // The CONSUME_DATA merge algorithm uses the template's default value only for the // first binding; all others update the form node from the data. if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA && dataNode.isMapped()) bUpdateData = false; // The MATCH_TEMPLATE merge algorithm always uses the template's default value anytime // it finds one, so that a single binding can set a default whether it happens to be // first or not. if (mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE && formNode instanceof FormField) { FormField formField = (FormField)formNode; Field templateField = (Field)(formField.getProto()); if (StringUtils.isEmpty(templateField.getRawValue())) bUpdateData = false; } } // check for placeholder flag - this means node was created by the dataDescription // during the merge as a side-effect of another node and should therefore be treated as // a newly created node needing FILL if (dataNode instanceof DataNode && ((DataNode)dataNode).getIsDDPlaceholder()) { // clear place holder flag when the node is used ((DataNode)dataNode).setIsDDPlaceholder(false); bUpdateData = true; } if (formNode instanceof FormField) { ((FormField)formNode).setDataNode(dataNode, bUpdateData, bPeer, "", true); } else if (formNode instanceof FormSubform) { ((FormSubform)formNode).setDataNode(dataNode, true); } else if (formNode instanceof FormExclGroup) { ((FormExclGroup)formNode).setDataNode(dataNode, bUpdateData, bPeer); } else { return; } outputTraceMessage(ResId.FormNodeMatchedTrace, formNode, dataNode, ""); } /** @exclude from published api. */ boolean calculationsPending() { return mPendingCalculateNodes.size() > 0; } /** * Determines whether we should queue up calcs/validations for the given node. * Cases we watch for are: * a) if the object is inactive we don't queue * b) if adding the node to the moPendingCalculateNodes (if bCalculate is TRUE) * or moPendingValidateNodes (if bCalculate is FALSE) will create cyclic dependency or * duplicate. */ private boolean canBeQueued(Node node, boolean bCalculate) { // ensure the node we are looking at is attached to the doc if (node.getXFAParent() == null) return false; // Inactive objects do not fire their calculations or validations, as documented in // https://zerowing.corp.adobe.com/display/xtg/InactivePresenceXFAProposal // To mitigate performance on older forms, only do this check for XFA 3.0 documents and higher. if ((mTemplateModel != null) && (mTemplateModel.getOriginalXFAVersion() >= Schema.XFAVERSION_30) && (node instanceof Container)) { Container container = (Container)node; int ePresence = container.getRuntimePresence(EnumAttr.UNDEFINED); if (EnumAttr.PRESENCE_INACTIVE == ePresence) return false; } List list = bCalculate ? mPendingCalculateNodes : mPendingValidateNodes; int nStart = bCalculate ? mnNextPendingCalculateNode : mnNextPendingValidateNode; // There are two purposes to this first loop: // // 1) Detect cyclic dependencies. Scripts in the list in the range from nStart // to the end of the list are pending. Scripts from the range 0 to nStart-1 // already have been executed in the current recalculation. If we find that // the script has been previously executed CYCLE_MAX times, just stop & don't // queue it up again to prevent an infinite loop. // // 2) Search for a duplicate. Don't add it if it's already there. // Duplicates would occur when a dependent changes for a script which is already queued // up to run later. In this case we don't want to re-fire the later calculation // because it's queued up to run anyway. // // Note: Return true if another script was queued up and false otherwise. if (! mbSkipCyclicAndDuplicateCheck) { // Skip for performance prototyping. int nCycleCount = 0; for (int i = 0; i < list.size(); i++) { if (list.get(i) == node) { if (i < nStart) { // It's less then nStart (meaning that the script has already been executed), // so just keep an eye out for cyclic dependencies (i.e. calcs that depend // on each other). if (++nCycleCount > CYCLE_MAX) return false; // cycle detected -- just stop } else { // It's >= nStart, meaning a duplicate was found in the pending calcs. // No need to add it again. return false; } } } } return true; } private void checkForItems(FormField field, Node dataMatch) { // THIS IS A HACK FOR FORM SERVER!!!!!!!!!!!! Element ui = field.getElement(XFA.UITAG, true, 0, false, false); if (ui != null) { // get current ui Node currentUI = ui.getOneOfChild(true, false); if (currentUI != null) { if (currentUI.isSameClass(XFA.CHOICELISTTAG)) { // if we match a value to a choice list, it is // potentially followed by in the overlay // data. This is a list of values to merge into the // choicelist. NodeList dataChildren = dataMatch.getNodes(); int nNodes = dataChildren.length(); boolean bFoundItems = false; Node itemsDataNode = null; for (int i = 0; i < nNodes; i++) { itemsDataNode = (Node)dataChildren.item(i); String sName = itemsDataNode.getName(); if (sName.compareToIgnoreCase("items") == 0) { bFoundItems = true; break; } } if (!bFoundItems || itemsDataNode == null) return; consumeDataNode(null, itemsDataNode, FormModel.DatasetSelector.MAIN_DATASET); NodeList dataItemsChildren = itemsDataNode.getNodes(); int n = dataItemsChildren.length(); Field.ItemPair itemPair = new Field.ItemPair(); // Retrieve the bound and text item lists. field.getItemLists(false, itemPair, true); Items displayItems = itemPair.mDisplayItems; Items saveItems = itemPair.mSaveItems; // okay - ready to go - clear existing list items first if (displayItems != null) displayItems.clearItems(false); if (saveItems != null) saveItems.clearItems(false); int i = 0; while (i < n) { Node dataChild = (Node)dataItemsChildren.item(i); consumeDataNode(null, dataChild, FormModel.DatasetSelector.MAIN_DATASET); String aChildName = dataChild.getName(); if (aChildName == null) { i++; continue; } String sSaveValue = ((DataNode)dataChild).getValue(); // NOTE: this assumes either // // a // A // b // B // ... // // or // // a // b // ... // // // it does not take care of multiple occurrences of // 'display' under 'save' i++; Node displayChild = (Node)dataItemsChildren.item(i); consumeDataNode(null, displayChild, FormModel.DatasetSelector.MAIN_DATASET); String aDisplayChildName = displayChild.getName(); String sDisplayValue; if (aDisplayChildName == XFA.SAVE) sDisplayValue = sSaveValue; else sDisplayValue = ((DataNode)displayChild).getValue(); if (displayItems != null) displayItems.addItem(sDisplayValue, false); if (saveItems != null && saveItems != displayItems) saveItems.addItem(sSaveValue, false); i++; }// endwhile }// endif choiceList }// endif currentUI }// endif ui } /** * Removes any leader or trailer nodes under this node. * * @exclude from published api. */ void cleanupLayoutNodes() { // remove any static nodes from the form DOM while (mLayoutContent.size() > 0) { Element last = mLayoutContent.get(mLayoutContent.size() - 1).mNode; mLayoutContent.remove(mLayoutContent.size() - 1); if (null != last) { // reset parents leader/trailer counts; Node parent = last.getXFAParent(); if (parent != null && parent.getModel() != null) { // if we have a parent and that parent is valid if (parent.isSameClass(XFA.SUBFORMSETTAG)) { ((SubformSet)parent).reset(); } else if (parent.isSameClass(XFA.SUBFORMTAG)) { ((Subform)parent).reset(); } // *sigh* Don't remove if null parent, else throws exception // (which seems a little extreme to me) last.remove(); // cleanup and data nodes tied to oLast // also ensure that all the children have a null model, this way we // can ensure that we don't run any calcs or validates on these // nodes, this also removes any peers and events tied to // this node removeReferencesImpl(last, false); } } } mCurrentPageSet = null; } /** * Removes any layout-generated nodes from under the page area. * This will be all leader and trailer subforms. * * @exclude from published api. */ void cleanupLayoutNodes(Node node) { // Need to retain one child ahead in the loop because removing the current // child will also remove the link to the next sibling. Node nextChild; for (Node child = node.getFirstXFAChild(); child != null; child = nextChild) { nextChild = child.getNextXFASibling(); if (child.isContainer()) { // don't process draws or fields if (child.isSameClass(XFA.DRAWTAG) || child.isSameClass(XFA.FIELDTAG)) continue; if (child instanceof FormSubform && ((FormSubform)child).isLayoutNode() ) { if (node.getModel() != null) { // only need to remove the node if the parent is valid // remove for form DOM child.remove(); // cleanup and data nodes tied to pChild // also ensure that all the children have a null model, this way we // can ensure that we don't run any calcs or validates on these // nodes, this also removes any peers and events tied to this node removeReferences(child); } continue; // for loop } // recursive call cleanupLayoutNodes(child); } } } /** * Resets the focus. No field will have focus after but the form model will * remember which field had the focus last. * * @exclude from published api - UI methods not relevant for server code. */ public void clearFocus() { mPrevActiveField = mActiveField; mActiveField = null; } /** * Loads the serialized form DOM into mDeltas. */ private String computeCheckSum() { // JavaPort: In the C++ implementation, a PKI_Base-derived object must // be set using XFAFormModelImpl::setPKI, or the checksum is not // calculated. The PKI_Base class is not ported since this is the only // place where it is used at this level, and the hash function is always // SHA-1, which is trivial to invoke directly via JCE. ByteArrayOutputStream tempStream = new ByteArrayOutputStream(); DOMSaveOptions formStateOptions = new DOMSaveOptions(); formStateOptions.setExcludePreamble(true); formStateOptions.setExpandElement(true); formStateOptions.setDisplayFormat(DOMSaveOptions.RAW_OUTPUT); // Note: don't need to canonicalize, we simply must save the template and data streams // if there are any runtime differences then the formstate is invalid. // // UPDATE: not entirely true. In order to validate our checksums we have to write out // the same thing that we're going to get when we read it back in. Since our parser // canonicalizes the order of namespaces when reading them in, we have to at least do // that much when writing them out. Watson 1370356 formStateOptions.setCanonicalizeNamespaceOrder(true); // Get an instance of a SHA-1 digest function java.security.MessageDigest md; try { md = java.security.MessageDigest.getInstance("SHA-1"); } catch (java.security.NoSuchAlgorithmException ex) { assert false; return ""; } // save out template DOM mTemplateModel.saveXML(tempStream, formStateOptions); md.update(tempStream.toByteArray()); tempStream.reset(); // save out data DOM mDataModel.saveXML(tempStream, formStateOptions); md.update(tempStream.toByteArray()); tempStream = null; byte[] hash = md.digest(); return Base64.encode(hash, false); } private int countOverlayDataChild(Node formNode, Node dataParent) { int nCount = 0; for (Node dataChild = dataParent.getFirstXFAChild(); dataChild != null; dataChild = dataChild.getNextXFASibling()) { // check if the data node and proto node are mappable if (isMappableForOverlayData(formNode, dataChild, false)) nCount++; } return nCount; } /** * Recursively binds the XFA Data DOM node with an XFA Template DOM node. * * @param templateNode * the template node to match * @param dataNode * the data node to match * @param formParent * the parent we'll add created form nodes to * @param connectionDataParent * the connection data parent * * @exclude from published api. */ void createAndMatchChildren(Element templateNode, DataNode dataNode, Element formNode, Element connectionDataParent) { if (formNode.isContainer() && connectionDataParent != null) { setConnectionDataContextInfo((Container)formNode, connectionDataParent); } // stop if we are not a container or if we are a leaf node if (!formNode.isContainer() || formNode.isSameClass(XFA.FIELDTAG) || formNode.isSameClass(XFA.DRAWTAG)) return; boolean bMatchDescendantsOnly = getMatchDescendantsOnly(); if (!bMatchDescendantsOnly) { int eMergeType = mergeType(templateNode, "", null); switch (eMergeType) { case EnumAttr.MATCH_GLOBAL: case EnumAttr.MATCH_DESCENDANT: case EnumAttr.MATCH_DATAREF: { setMatchDescendantsOnly(true); break; } } } // For each child of template prototype node for (Node templateChild = templateNode.getFirstXFAChild(); templateChild != null; templateChild = templateChild.getNextXFASibling()) { if (!(templateChild instanceof Element)) continue; Element templateElement = (Element)templateChild; // Check if this is a template node we should map if (mapChild(templateElement, formNode)) { // match the protoChild against the data createAndMatchNode(templateElement, dataNode, formNode, connectionDataParent); } } // reset setMatchDescendantsOnly(bMatchDescendantsOnly); } // Adobe patent application tracking # P624, entitled "Form-based Data Storage And Retrieval", inventors: Matveief,Young,Solc /** * Recursively binds the XFA Data DOM nodes with an XFA Template DOM node. * * @param templateNode * the template node to match * @param dataParent * the data parent to match from * @param formParent * the parent we'll add created form nodes to * @param nStartDataIndex * start index for searching * @return the number of created nodes * * @exclude from published api. */ int createAndMatchNode(Element templateNode, DataNode dataParent, Element formParent, Element connectionDataParent) { int nNumCreated = 0; if (templateNode instanceof Subform) { // create instanceManager for this subform FormInstanceManager instanceManager = createInstanceManager(templateNode, formParent); // ensure we can create the node int nMax = getOccurAttribute(templateNode, XFA.MAXTAG); if ((nMax != ChildReln.UNLIMITED) && (nMax < 1)) return 0; int nRequired = getOccurAttribute(templateNode, XFA.MINTAG); // get the form info Container.FormInfo formInfo = getFormInfo(templateNode, dataParent, connectionDataParent); DataNode dataMatch = findMatch(formInfo, true, FormModel.DatasetSelector.MAIN_DATASET); while (dataMatch != null) { // Found a match! nNumCreated++; if (!formInfo.bConnectDataRef) { // the dataMatch is a new data parent Element formNode = createFormNode(templateNode, formParent, instanceManager); bindNodes(formNode, dataMatch, false); consumeDataNode(formInfo, dataMatch, FormModel.DatasetSelector.MAIN_DATASET); resetLocalConsumptionContext(templateNode); createAndMatchChildren(templateNode, dataMatch, formNode, connectionDataParent); } else { // the dataMatch is the new connectionDataParent consumeDataNode(formInfo, dataMatch, FormModel.DatasetSelector.MAIN_DATASET); DataNode actualDataMatch = findMatch(formInfo, true, FormModel.DatasetSelector.ALT_DATASET); if (actualDataMatch != null) { Element formNode = createFormNode(templateNode, formParent, instanceManager); resetLocalConsumptionContext(templateNode); createAndMatchChildren(templateNode, actualDataMatch, formNode, dataMatch); // push the connection data back to pre-existing data DOM nodes bindNodes(formNode, actualDataMatch, UPDATE_DATA); consumeDataNode(formInfo, actualDataMatch, FormModel.DatasetSelector.ALT_DATASET); } else { Element formNode = createFormNode(templateNode, formParent, instanceManager); resetLocalConsumptionContext(templateNode); createAndMatchChildren(templateNode, dataParent, formNode, dataMatch); } } // max number of objects reached stop merging if ((nMax != ChildReln.UNLIMITED) && (nNumCreated == nMax)) return nNumCreated; formInfo = getFormInfo(templateNode, dataParent, connectionDataParent); dataMatch = findMatch(formInfo, true, FormModel.DatasetSelector.MAIN_DATASET); } // Our current subform might just be a structural or layout element, so if it has no binding // itself, check to see if it has any children which match the data. // Note that in legacy mode (MERGEMODE_CONSUMEDATA), we do this regardless of whether or not // the subform has a binding, which produces some sub-optimal results. if (formInfo.eMergeType == EnumAttr.MATCH_NONE || mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) { while (hasDescendantMatch(templateNode, dataParent, connectionDataParent, formInfo.eMergeType, false, null)) { nNumCreated++; Element formNode = createFormNode(templateNode, formParent, instanceManager); // Note: poFormNode doesn't get bound to anything; it's just here to hold its children // Note: we don't reset the local consumption context since we didn't bind createAndMatchChildren(templateNode, dataParent, formNode, connectionDataParent); // max number of objects reached stop merging if ((nMax != ChildReln.UNLIMITED) && (nNumCreated == nMax)) break; } } if (nNumCreated == 0) nRequired = getOccurAttribute(templateNode, XFA.INITIALTAG); // create the remaining required subforms while (nNumCreated < nRequired) { // A CONSUME_DATA merge uses a second pass for data creation so all we need to do here is create // empty form nodes. // A MATCH_TEMPLATE merge does everything in a single pass so we need to create the form and // data nodes and recurse into our children. // if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) createEmptyFormNode(templateNode, formParent, connectionDataParent, instanceManager); else { Element formNode = createFormNode(templateNode, formParent, instanceManager); dataMatch = null; if (formInfo.bAssociation) { // Someday we may create associations at runtime, but that day is not today. } else if (formInfo.eMergeType == EnumAttr.MATCH_NONE) { // No data to create/bind, but we need to process children relative to parent's data: dataMatch = dataParent; } else if (mapChild(templateNode, formParent)) { dataMatch = createDataNode(formNode, dataParent, formInfo.eMergeType, true); bindNodes(formNode, dataMatch, UPDATE_DATA); consumeDataNode(null, dataMatch, DatasetSelector.MAIN_DATASET); } // Continue down with the new data context: createAndMatchChildren(templateNode, dataMatch, formNode, connectionDataParent); } nNumCreated++; } } else if (templateNode instanceof Field) { FormField formNode = (FormField)createFormNode(templateNode, formParent, null); // get the form info Container.FormInfo formInfo = getFormInfo(templateNode, dataParent, connectionDataParent); assert formInfo != null; DataNode dataMatch = findMatch(formInfo, false, FormModel.DatasetSelector.MAIN_DATASET); if (formInfo.bConnectDataRef && dataMatch != null) { // the dataMatch is a new data parent // update the form node before creating/finding the data node formNode.setDataNode(dataMatch, false, false, "", true); consumeDataNode(formInfo, dataMatch, FormModel.DatasetSelector.MAIN_DATASET); setConnectionDataContextInfo(formNode, dataMatch); DataNode actualDataMatch = findMatch(formInfo, false, FormModel.DatasetSelector.ALT_DATASET); if (actualDataMatch != null) { // push the connection data back to pre-existing data DOM nodes bindNodes(formNode, actualDataMatch, UPDATE_DATA); consumeDataNode(formInfo, actualDataMatch, FormModel.DatasetSelector.ALT_DATASET); } } else { if (dataMatch == null) { // We've already looked at attributes of poDataParent, but if we're MATCH_ONCE or // MATCH_DESCENDANT then we also want to look at nested attributes. if (formInfo.eMergeType == EnumAttr.MATCH_ONCE || formInfo.eMergeType == EnumAttr.MATCH_DESCENDANT) dataMatch = findNestedAttrMatch(formNode, dataParent, formInfo.eMergeType); } // A CONSUME_DATA merge uses a second pass for data creation; a MATCH_TEMPLATE merge // does everything in a single pass. // if (dataMatch != null|| mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) { bindNodes(formNode, dataMatch, false); consumeDataNode(formInfo, dataMatch, DatasetSelector.MAIN_DATASET); } else { dataMatch = createDataNode(formNode, dataParent, formInfo.eMergeType, true); bindNodes(formNode, dataMatch, UPDATE_DATA); consumeDataNode(null, dataMatch, DatasetSelector.MAIN_DATASET); } } nNumCreated++; } else if (templateNode instanceof SubformSet) { // start subformset nNumCreated += mapSubformSet(templateNode, formParent, dataParent, connectionDataParent); } else if (templateNode instanceof ExclGroup) { // get the form info Container.FormInfo formInfo = getFormInfo(templateNode, dataParent, null); if (formInfo.eMergeType == EnumAttr.MATCH_NONE) { // ExclGroup not bound, but children might be Element formNode = createFormNode(templateNode, formParent, null); // Note: we don't reset the local consumption context since we didn't bind createAndMatchChildren(templateNode, dataParent, formNode, connectionDataParent); } else { // once, global, dataref DataNode dataMatch = findMatch(formInfo, false, FormModel.DatasetSelector.MAIN_DATASET); if (dataMatch == null) { // A CONSUME_DATA merge uses a second pass for data creation; a MATCH_TEMPLATE merge // does everything in a single pass. // if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) createEmptyFormNode(templateNode, formParent, connectionDataParent, null); else { Element formNode = (Element)createEmptyFormNode(templateNode, formParent, connectionDataParent, null); dataMatch = createDataNode(formNode, dataParent, formInfo.eMergeType, true); bindNodes(formNode, dataMatch, UPDATE_DATA); consumeDataNode(null, dataMatch, DatasetSelector.MAIN_DATASET); } } else if (dataMatch.getClassTag() == XFA.DATAGROUPTAG) { Element formNode = createFormNode(templateNode, formParent, null); bindNodes(formNode, dataMatch, false); consumeDataNode(formInfo, dataMatch, FormModel.DatasetSelector.MAIN_DATASET); resetLocalConsumptionContext(templateNode); createAndMatchChildren(templateNode, dataMatch, formNode, connectionDataParent); } else { // data value Node formNode = createEmptyFormNode(templateNode, formParent, connectionDataParent, null); bindNodes(formNode, dataMatch, false); consumeDataNode(formInfo, dataMatch, FormModel.DatasetSelector.MAIN_DATASET); } } nNumCreated++; } else { // everything else Element formNode = createFormNode(templateNode, formParent, null); // Note: formNode is a structural element which isn't itself bound to anything // Note: we don't reset the local consumption context since we didn't bind createAndMatchChildren(templateNode, dataParent, formNode, connectionDataParent); nNumCreated++; } return nNumCreated; } /** * Creates a new DataNode for a corresponding form node (i.e a form node * with no DataNode). * * @param formNode * the node to match with data node * @param dataParent * the node to which the new DataNode will be appended. (the * context node if bDataRef is true) * @param eMergeType * the type of merge for the form node * @param bSearchForNewlyCreatedFirst * true if a search of newly-created nodes is required first * @return the created DataNode * @exclude from published api. */ DataNode createDataNode(Element formNode, Element dataParent, int eMergeType, boolean bSearchForNewlyCreatedFirst) { DataNode newDataNode = null; // Not mergeable if (eMergeType == EnumAttr.MATCH_NONE) return null; //#ifdef _DEBUG // JEY TODO: remove this once the new merge algorithm has a few hundred hours on it. // don't process nodes under mapped exclgroups // JavaPort TODO: using Assertions.isEnabled since we can't use // pre-processor instructions here if (Assertions.isEnabled) { Node mappedParent = getMappedParent(formNode); if (mappedParent instanceof FormExclGroup) { assert(false); return null; } } //#endif if (eMergeType == EnumAttr.MATCH_GLOBAL) { // create global boolean bUseDV = useDV(formNode); newDataNode = (DataNode)resolveCreateGlobal(formNode, bSearchForNewlyCreatedFirst, bUseDV); } else if (eMergeType == EnumAttr.MATCH_DATAREF) { // will bind the new data node to the field String sRef = getDataRef(formNode, "", null); boolean bUseDV = useDV(formNode); // The CONSUME_DATA merge algorithm matches "foo[*]" with the correct instance of foo by // skipping those that have already been mapped (which is handled inside resolveCreate). // // The MATCH_TEMPLATE merge algorithm matches based on index, and is also stricter about // enforcing matchDescendantsOnly. // if (mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) { if (somIsStar(sRef) && formNode instanceof FormSubform) { FormSubform formSubform = (FormSubform)formNode; sRef = sRef.substring(0, sRef.length() - 3); sRef += "[" + formSubform.getInstanceIndex(null) + "]"; } if (getMatchDescendantsOnly() && somIsRelative(sRef) && sRef.charAt(0) != '$') sRef = "$." + sRef; } newDataNode = resolveCreateDataRef(sRef, dataParent, bSearchForNewlyCreatedFirst, bUseDV); if (newDataNode != null) { if (!isMappable(formNode, newDataNode, false, false)) { MsgFormatPos msg = new MsgFormatPos(ResId.FormInvalidDataRef); msg.format(sRef); msg.format(formNode.getName()); msg.format(newDataNode.getClassAtom()); throw new ExFull(msg); } } } else if (eMergeType == EnumAttr.MATCH_ONCE || eMergeType == EnumAttr.MATCH_DESCENDANT) { // There are two template containers which impact the data hierarchy: // Subform and Field. If they occur, create data nodes to match. String aName = formNode.getName(); // ensure we have a name if (aName == "") return null; boolean bUseDV = useDV(formNode); if (dataParent == null) { // no data context; can't create } else if (mDataDescription != null) { // watson bug 1325319 we must escape "." chars in the name. E.G // foo.bar must be foo\.bar StringBuilder sTemp = new StringBuilder(aName); int fromIndex = 0; while (true) { int nDot = sTemp.indexOf(".", fromIndex); if (nDot == -1) break; sTemp.insert(nDot, '\\'); fromIndex = nDot + 2; } // Look for either the next free data node (CONSUME_DATA merge) or an index-matched // data node (MATCH_TEMPLATE merge). // StringBuilder sName = new StringBuilder(); if (getMatchDescendantsOnly()) // watson bug 1302087 sName.append("$."); sName.append(sTemp); if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) sName.append("[*]"); else sName.append("[" + formNode.getIndex(true) + "]"); // search (data node might have already been created by another node or from the DD code) newDataNode = resolveCreateDataRef(sName.toString(), dataParent, bSearchForNewlyCreatedFirst, bUseDV); } else { if (bUseDV) newDataNode = (DataNode)mDataModel.createNode(XFA.DATAVALUETAG, dataParent, aName, null, true); else newDataNode = (DataNode)mDataModel.createNode(XFA.DATAGROUPTAG, dataParent, aName, null, true); // If we're not allowed to create data (mbAdjustData), then we need to mark this node as // a default so it won't get saved out. (We do go ahead and create the node so that if // multiple fields bind to it they'll all share a single value.) if (!mbAdjustData) newDataNode.makeDefault(); } } // set attr on data node if (newDataNode != null) outputTraceMessage(ResId.NodeCreatedTrace, newDataNode, null, ""); else if (eMergeType != EnumAttr.MATCH_NONE) outputTraceMessage(ResId.UnmappedNode, formNode, null, ""); // Watson 1761270. // When doing an addInstance, and adding data, a barcode that was associated with this field was not // getting updated. This was because the data node did not notify it's parent that it was added and was // therefore not tracked as a dependency. // For scripts that reference data nodes, we need to send a notify peers message to the parent to // ensure that the dependencies are updated. if (mbMergeComplete && newDataNode != null && newDataNode.getXFAParent() != null) newDataNode.getXFAParent().notifyPeers(Peer.CHILD_ADDED, newDataNode.getClassAtom(), newDataNode); // set the data peer for the form node. return newDataNode; } /** * Merges a form node where there is no data node. This method will * recursively match all containers below the supplied form node. * * @param templateNode * the template node used to create the form node * @param formParent * the parent form node to merge the data with. * @param connectionDataParent * the connection data parent * @param instanceManager * the instanceManager for oProtoNode * * @exclude from published api. */ Element createEmptyFormNode(Element templateNode, Element formParent, Element connectionDataParent, FormInstanceManager instanceManager) { Element formNode = null; DataNode dataNode = null; // don't use the connect to determine merge type int eMergeType = mergeType(templateNode, "", null); // Check if this is a template node we should map if (mapChild(templateNode, formParent)) { // need to process global matches under the old merge algorithm. // since they don't propagate instances the need to be processed // the reason we don't don't care if adjust data is set is that // global nodes don't create instances, so always process them so we // get proper dynamic merge of their children if (eMergeType == EnumAttr.MATCH_GLOBAL && mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) { // search for a global match, if found create and match children dataNode = resolveGlobal(templateNode, true); if (dataNode != null) { // don't process if we have an exclgroup bound to a // DV, this will be done below if (!templateNode.isSameClass(XFA.EXCLGROUPTAG) || !dataNode.isSameClass(XFA.DATAVALUETAG)) { formNode = createFormNode(templateNode, formParent, instanceManager); bindNodes(formNode, dataNode, false); consumeDataNode(null, dataNode, FormModel.DatasetSelector.MAIN_DATASET); resetLocalConsumptionContext(templateNode); createAndMatchChildren(templateNode, dataNode, formNode, connectionDataParent); return formNode; } } } // create Form Node formNode = createFormNode(templateNode, formParent, instanceManager); } else return null; // stop if we are not a container or if we are an leaf node if (!templateNode.isContainer() || formNode.isSameClass(XFA.FIELDTAG) || formNode.isSameClass(XFA.DRAWTAG)) return formNode; // grab choice info boolean bChoice = false; if (templateNode instanceof SubformSet) { int eRelation = ((EnumValue)((SubformSet)templateNode).getAttribute(XFA.RELATIONTAG)).getInt(); if (eRelation == EnumAttr.RELATION_CHOICE) bChoice = true; } boolean bMatchDescendantsOnly = getMatchDescendantsOnly(); if (!bMatchDescendantsOnly) { switch (eMergeType) { case EnumAttr.MATCH_GLOBAL: case EnumAttr.MATCH_DESCENDANT: case EnumAttr.MATCH_DATAREF: { setMatchDescendantsOnly(true); break; } } } int nRequired = 1; // For each child of template prototype node for (Node templateChild = templateNode.getFirstXFAChild(); templateChild != null; templateChild = templateChild.getNextXFASibling()) { if (!(templateChild instanceof Element)) continue; Element templateElement = (Element)templateChild; // create instanceManager for this child FormInstanceManager newInstanceManager = createInstanceManager(templateElement, formNode); // the number of required occurrences of this element. nRequired = getOccurAttribute(templateElement, XFA.INITIALTAG); // create the remaining required subforms. for (int nCount = 0; nCount < nRequired; nCount++) { createEmptyFormNode(templateElement, formNode, connectionDataParent, newInstanceManager); } if (bChoice && templateChild.isContainer()) { // encountered the first break; } } // bind the new form node with the data node if there is one, must be done here // to ensure the exclgroups are set properly if (dataNode != null) { bindNodes(formNode, dataNode, false); consumeDataNode(null, dataNode, FormModel.DatasetSelector.MAIN_DATASET); } // reset setMatchDescendantsOnly(bMatchDescendantsOnly); return formNode; } /** * Indicates whether the current merge mode is globally consumptive * (XFAEnum::CONSUME_DATA) or not (XFAEnum::MATCH_TEMPLATE). * @exclude from published api. */ public int mergeMode() { return mbGlobalConsumption ? EnumAttr.MERGEMODE_CONSUMEDATA : EnumAttr.MERGEMODE_MATCHTEMPLATE; } /** * Creates a form node based on a template node * * @param templateNode * the template node used to create the form node * @param formParent * the parent to the new form node * @param instanceManager * the instanceManager associated with oProtoNode * @return the newly created node. * * @exclude from published api. */ Element createFormNode(Element templateNode, Element formParent, FormInstanceManager instanceManager) { assert(templateNode.getModel() instanceof TemplateModel); Element newFormNode; boolean bProtoChildren = !templateNode.isContainer(); boolean bNodeCanHaveEvents = false; if (templateNode.isSameClass(XFA.FIELDTAG)) { Element fieldElement = (Element)templateNode; boolean bIsMultiSelect = false; // validate the field if (mTemplateModel.validateGlobalField(fieldElement)) { Element ui = fieldElement.getElement(XFA.UITAG, true, 0, false, false); if (ui != null) { Node currentUI = ui.getOneOfChild(true, false); if (currentUI != null && currentUI.isSameClass(XFA.CHOICELISTTAG)) { EnumAttr eOpen = EnumAttr.getEnum(((Element)currentUI).getEnum(XFA.OPENTAG)); if (eOpen.getInt() == EnumAttr.MULTISELECT) { bIsMultiSelect = true; } } } if (bIsMultiSelect) newFormNode = new FormChoiceListField(formParent, null); else newFormNode = new FormField(formParent, null); } else { // it is an error if there is global field and non-global field // with the same name throw new ExFull(new MsgFormat(ResId.XFAGlobalFieldConflictException, templateNode.getName())); } bProtoChildren = true; bNodeCanHaveEvents = true; } else if (templateNode.isSameClass(XFA.SUBFORMTAG)) { newFormNode = new FormSubform(formParent, null); // Watson 1457810 - Update the root subform locale setting in the Form Model *not* the Template Model. // If the template node is the root subform update the locale setting of the root formSubform if (templateNode == mRootSubform) { mRootFormSubform = (FormSubform)newFormNode; // Adobe patent application tracking # B136, entitled "Applying locale behaviors to regions of a form", inventors: Gavin McKenzie, Mike Tardif, John Brinkman" // Update the ambient locale if specified in the // Configuration DOM settings. String sLocale = getAmbientLocale(); if (!StringUtils.isEmpty(sLocale)) { StringAttr oLocaleProp = new StringAttr(XFA.LOCALE, sLocale); ((FormSubform)newFormNode).setAttribute(oLocaleProp, XFA.LOCALETAG); } } if (instanceManager != null) instanceManager.addInstance(newFormNode, false); bNodeCanHaveEvents = true; } else if (templateNode.isSameClass(XFA.SUBFORMSETTAG)) { newFormNode = new FormSubformSet(formParent, null); if (instanceManager != null) instanceManager.addInstance(newFormNode, false); } else if (templateNode.isSameClass(XFA.EXCLGROUPTAG)) { newFormNode = new FormExclGroup(formParent, null); bNodeCanHaveEvents = true; } else { newFormNode = (Element)createNode(templateNode.getClassTag(), formParent, "", "", true); } if (newFormNode != null) { String aNodeName = templateNode.getName(); if (aNodeName != null && "" != aNodeName) newFormNode.privateSetName(aNodeName); outputTraceMessage(ResId.NodeCreatedTrace, newFormNode, null, ""); if (bProtoChildren) ((ProtoableNode)newFormNode).resolveProto((ProtoableNode)templateNode, false, false, false); else ((ProtoableNode)newFormNode).setProto((ProtoableNode)templateNode); newFormNode.makeDefault(); // process globals and data refs after for legacy merge algorithm. if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) { int eMergeType = mergeType(templateNode, "", null); switch (eMergeType) { case EnumAttr.MATCH_GLOBAL: case EnumAttr.MATCH_DESCENDANT: case EnumAttr.MATCH_DATAREF: { mExplicitMatchNodes.add(newFormNode); break; } } } // register calculate, validate and all events. Because fields will call resolveProto we // have to loop through its children and add events... for subform and exclgroup events the nodes // will get created with createFormNode we have to register the events when they get created. // watson 1109807: registration of events can be turn off. This happens when the layout // is peeking the overflow trailer. Basically the form tree gets created (so the layout can know its size) // but will never be attached to the form model so we shouldn't register the events. if (mbRegisterNewEvents && (newFormNode.isSameClass(XFA.EVENTTAG) || bNodeCanHaveEvents)) registerEvents(newFormNode, XFAEVENTTYPE_ALL); } return newFormNode; } private void createFormState() { // THIS IS A HACK FOR FORM SERVER!!!!!!!!!!!! // Create an "$xfa.formState" private, undocumented, // only-for-formServer packet // Currently, it contains only the item values of // all the choicelists on the form // // // // aAdobe // // // // if (!getFormStateUsage()) return; Element formState = (Element)getAppModel().locateChildByName("formState", 0); if (formState == null) { // Create the formState node. AppModel appModel = (AppModel)getXFAParent(); formState = new Packet(appModel, null); new ModelPeer((Element)appModel.getXmlPeer(), null, null, "formState", "formState", null, formState); } else { ModelPeer formStateDomPeer = (ModelPeer)((Packet)formState).getXmlPeer(); // blank out the formState packet Node node = formStateDomPeer.getFirstXFAChild(); while (node != null) { Node oNext = node.getNextXFASibling(); node.remove(); node = oNext; } } // run through the form model and write out all choicelist items // recurse through all children for (Node node = getFirstXFAChild(); node != null; node = node.getNextXFASibling()) { if (node instanceof Element) createFormState((Element)node, formState); } } private void createFormState(Element formNode, Element oFormState) { // THIS IS PART OF A HACK FOR FORM SERVER!!!!!!!!!!!! Document domDoc = oFormState.getOwnerDocument(); for (Node formChild = formNode.getFirstXFAChild(); formChild != null; formChild = formChild.getNextXFASibling()) { if (formChild instanceof FormField) { FormField field = ((FormField)formChild); // Get the UI tag Element ui = field.getElement(XFA.UITAG, true, 0, false, false); if (ui != null) { // get current ui Node currentUI = ui.getOneOfChild(true, false); if (currentUI != null) { if (currentUI.isSameClass(XFA.CHOICELISTTAG)) { // add items to formState Field.ItemPair itemPair = new Field.ItemPair(); field.getItemLists(true, itemPair, false); Node saveItem = itemPair.mSaveItems; Node displayItem = itemPair.mDisplayItems; boolean bModifiedList = false; NodeList saveItems = new ArrayNodeList(); int nSaveItems = 0; if (saveItem != null) { // watson bug 1573819, only create formstate info if the list values // live as a child of this form field and they are not considered default values // due to Watson 1430554 we must pass in false to is default other wise we ask if // template was a default and that always returns false if (!saveItem.isDefault(false) && saveItem.getXFAParent() == field ) bModifiedList = true; saveItems = saveItem.getNodes(); nSaveItems = saveItems.length(); } NodeList displayItems = new ArrayNodeList(); int nDisplayItems = 0; if (displayItem != null) { // watson bug 1573819, only create formstate info if the list values // live as a child of this form field and they are not considered default values // due to Watson 1430554 we must pass in false to is default other wise we ask if // template was a default and that always returns false if (!displayItem.isDefault(false) && displayItem.getXFAParent() == field ) bModifiedList = true; displayItems = displayItem.getNodes(); nDisplayItems = displayItems.length(); } // if the lists haven't been modified then we don't need to save them if (!bModifiedList) continue; int nValues = 0; if ((nSaveItems != 0 && nDisplayItems != 0) && (nSaveItems != nDisplayItems)) { // bad user! nValues = (nSaveItems < nDisplayItems) ? nSaveItems : nDisplayItems; } else { nValues = nSaveItems; } if (nValues == 0) continue; // create element under Element newState = domDoc.createElementNS("", "state", oFormState); // add ref attribute // String sRef = pField.getSomName(); String sRef = field.getSOMExpression(this, false); newState.setAttribute("", "ref", "ref", sRef); // create element under Element newItems = domDoc.createElementNS("", "items", newState); // get save/display values String sDisplay = ""; String sSave = ""; TextNode textNode; for (int k = 0; k < nValues; k++) { if (saveItems.item(k) instanceof TextNode) { textNode = (TextNode)saveItems.item(k); sSave = textNode.getValue(); } if (displayItems.item(k) instanceof TextNode) { textNode = (TextNode)displayItems.item(k); sDisplay = textNode.getValue(); } // create and elements under Element save = domDoc.createElementNS("", "save", newItems); new TextNode(save, null, sSave); Element display = domDoc.createElementNS("", "display", newItems); new TextNode(display, null, sDisplay); }// endfor }// endif (poCurrentUI instanceof // JF_CLS_XFACHOICELIST) } } } else if (formNode.isContainer()) { createFormState((Element)formChild, oFormState); } }// endfor } /** * Creates an FormInstanceManager based on a template Subform node * * @param templateNode * the template node used to create the form node * @param formParent * the parent to the new form node * @return the newly created node. */ private FormInstanceManager createInstanceManager(Element templateNode, Element formParent) { // XFAF only supports instance managers on "page level subforms" if (mbIsXFAF) { if (!(templateNode instanceof Subform) || templateNode.getXFAParent() != mRootSubform ) return null; } if ( ( templateNode instanceof Subform || templateNode instanceof SubformSet ) && ( formParent instanceof FormSubform || formParent instanceof FormSubformSet || formParent.isSameClass(XFA.PAGEAREATAG)) ) { FormInstanceManager formInstanceManager = new FormInstanceManager(formParent, null); formInstanceManager.setTemplateNode((ProtoableNode)templateNode); formInstanceManager.setMatchDescendantsOnly(getMatchDescendantsOnly()); formInstanceManager.makeDefault(); return formInstanceManager; } return null; } /** * Creates a layout content node. This node will be an invisible child of * the form model. So its contents can refrence other nodes in the model * however other nodes can't reference it. * * @param staticContent * the static content * @param parent * parent for the new node * @return the Static form node */ private ProtoableNode createLayoutNode(ProtoableNode staticContent, Element parent) { // Clone Node and added to the static content list in XFAFormModel assert staticContent != null; // right now this should only be called with subforms, subformSets or pageareas. assert staticContent instanceof PageSet || staticContent instanceof Subform || staticContent instanceof SubformSet; Element newStaticContent = importNode(staticContent, parent, !(staticContent instanceof PageSet)); if (parent != null) { mLayoutContent.add(new LayoutContentInfo(newStaticContent)); } if (newStaticContent instanceof FormSubform) ((FormSubform)newStaticContent).setLayoutNode(); else if (newStaticContent instanceof FormSubformSet) { // If it's a subformset, recursively flag all subform children as 'layout' nodes setLayoutNodes((FormSubformSet)newStaticContent); } return (ProtoableNode)newStaticContent; } /** * Creates a leader or trailer Subform * * @param sReference * SOM expression or id ref pointing to the Subform or SubformSet * to create * @param container * parent context under which to create the leader/trailer * @param bPeek * false if the node is to be appended to this * @return the leader or trailer * * @exclude from published api. */ Node createLeaderTrailer(String sReference, Container container, boolean bPeek) { // watson 1109807: we don't want to register any events if we are just peeking // nIndex passed by reference because this is the only break element that will be modified. mbRegisterNewEvents = !bPeek; try { // watson 1569074 Before asking the template model to create the leader/trailer, // be sure we are passing in the template context of the node. // This is critical for proper SOM resolution etc. Node containerTemplateContext = container; ProtoableNode protoableContainer = (ProtoableNode)container; while (protoableContainer != null && !(protoableContainer.getModel() instanceof TemplateModel)) { protoableContainer = protoableContainer.getProto(); } containerTemplateContext = protoableContainer; assert containerTemplateContext != null; ProtoableNode templateSF = null; if (containerTemplateContext != null) templateSF = mTemplateModel.createLeaderTrailer(sReference, containerTemplateContext, bPeek); if (templateSF != null) { // We use container here (not containerTemplateContext), since // it is to be the parent of the new node in the form model. Node newNode; if (bPeek) newNode = createLayoutNode(templateSF, null); else newNode = createLayoutNode(templateSF, container); return newNode; } return null; } finally { mbRegisterNewEvents = true; } } /** * @see Model#createNode(int, Element, String, String, boolean) * * @exclude from published api. */ @FindBugsSuppress(code="ES") public Node createNode(int eClassTag, Element parent, String aNodeName, String aNS, boolean bDoVersionCheck) { assert(aNodeName != null); assert(aNS != null); Element newFormNode = null; // Watson bug 1099100 // only create container Form nodes during merge! if (!mbAllowNewNodes && ( eClassTag == XFA.FIELDTAG || eClassTag == XFA.SUBFORMTAG || eClassTag == XFA.SUBFORMSETTAG || eClassTag == XFA.EXCLGROUPTAG || eClassTag == XFA.DRAWTAG || eClassTag == XFA.AREATAG || eClassTag == XFA.PAGEAREATAG || eClassTag == XFA.PAGESETTAG)) return newFormNode; assert eClassTag != XFA.XMLMULTISELECTNODETAG; if (eClassTag == XFA.FIELDTAG) { newFormNode = new FormField(parent, null); } else if (eClassTag == XFA.SUBFORMTAG) { newFormNode = new FormSubform(parent, null); } else if (eClassTag == XFA.EXCLGROUPTAG) { newFormNode = new FormExclGroup(parent, null); } else if (eClassTag == XFA.SUBFORMSETTAG) { newFormNode = new FormSubformSet(parent, null); } else { newFormNode = getSchema().getInstance(eClassTag, this, parent, null, bDoVersionCheck); } if (newFormNode != null && aNodeName != "") { newFormNode.privateSetName(aNodeName); } return newFormNode; } private void createOverlayData(Node formNode) { // THIS IS A HACK FOR FORM SERVER!!!!!!!!!!!! // The overlay data written out here must be for the currentPage only. // It will not be flat, but will be in the structure of the Form Dom. // It will contain field values and choiceList items. // // // // // // 1.20$1.20 // AAAardvarkBBaboon // // // // // // do this when output.type=mergedXDP and destination=webClient Element overlayDataNode = null; // We will always have at least one data node, so we only // need to look for overlayData if length is greater than 1 for (Node node = mDataModel.getFirstXFAChild(); node != null; node = node.getNextXFASibling()) { if (node.getName() == "overlayData") { overlayDataNode = (Element)node; break; } } if (overlayDataNode != null) { // remove it and create an empty oDataListoDataListt.remove(oOverlayDataNode); } // create the xfa.dataSets.overlayData node overlayDataNode = (Element)mDataModel.createNode(XFA.DATAGROUPTAG, mDataModel, "overlayData", mDataModel.getNS(), true); // formNode should be a subform Element panelDataNode = null; if (formNode instanceof FormSubform) { String aName = formNode.getName(); if (aName == "") { // cannot create a nameless dataGroup, so just omit creating one for // the nameless second level subform panelDataNode = overlayDataNode; } else { // create the dataGroup under overlayData that corresponds // to the second level subform ("panel") panelDataNode = (Element)mDataModel.createNode(XFA.DATAGROUPTAG, overlayDataNode, aName, "", true); } } createOverlayData(formNode, panelDataNode); } private void createOverlayData(Node form, Element dataParent) { // THIS IS A HACK FOR FORM SERVER!!!!!!!!!!!! // Create the structured overlayData Element newDataNode; for (Node formChild = form.getFirstXFAChild(); formChild != null; formChild = form.getNextXFASibling()) { String aChildName = formChild.getName(); if (formChild instanceof FormField) { FormField field = ((FormField)formChild); String sRawValue = field.getRawValue(); // add node, value, formatted value and items // oNewDataNode = mpoDataModel.createElement(XFA.DATAVALUETAG, poDataParentImpl, sChildName); // ((XFADataValueImpl*)oNewDataNode).setValue(sRaw,false); // create the dataGroup for the field newDataNode = (Element)mDataModel.createNode(XFA.DATAGROUPTAG, dataParent, aChildName, "", true); // add the raw value if (form instanceof FormChoiceListField) { // multiselect choice list has multiple dataValues if (formChild.isPropertySpecified(XFA.VALUETAG, true, 0)) { Value value = (Value)((FormChoiceListField)formChild).getElement(XFA.VALUETAG, 0); Node valueContent = value.getOneOfChild(); if (valueContent instanceof ExDataValue) { List selectionList = new ArrayList(); Node node = ((ExDataValue)valueContent).getOneOfChild(); if (node instanceof XMLMultiSelectNode) { XMLMultiSelectNode multiSelect = (XMLMultiSelectNode)node; multiSelect.getValues(selectionList); int nNumberSelected = selectionList.size(); for (int j = 0; j < nNumberSelected; j++) { sRawValue = selectionList.get(j); DataNode rawValue = (DataNode)mDataModel.createNode(XFA.DATAVALUETAG, newDataNode, "value", "", true); rawValue.setValue(sRawValue, true); }// endfor } } } } else { DataNode rawValue = (DataNode)mDataModel.createNode(XFA.DATAVALUETAG, newDataNode, "value", "", true); rawValue.setValue(sRawValue, true); }// endif multiSelectChoiceList // possibly add the formatted value String sFormattedValue = field.getFormattedValue(); if (!sFormattedValue.equals(sRawValue)) { // write it out as a data value DataNode oFormattedValue = (DataNode)mDataModel.createNode(XFA.DATAVALUETAG, newDataNode, "formattedValue", "", true); oFormattedValue.setValue(sFormattedValue, true); } // look for items children // Get the UI tag Element ui = field.getElement(XFA.UITAG, true, 0, false, false); if (ui != null) { // get current ui Node currentUI = ui.getOneOfChild(true, false); if (currentUI != null) { if (currentUI.isSameClass(XFA.CHOICELISTTAG)) { // add items to overlayData Field.ItemPair itemPair = new Field.ItemPair(); field.getItemLists(true, itemPair, false); Node displayItem = itemPair.mDisplayItems; Node saveItem = itemPair.mSaveItems; NodeList saveItems; int nSaveItems; if (saveItem != null) { saveItems = saveItem.getNodes(); nSaveItems = saveItems.length(); } else { saveItems = new ArrayNodeList(); nSaveItems = 0; } NodeList displayItems; int nDisplayItems; if (displayItem != null) { displayItems = displayItem.getNodes(); nDisplayItems = displayItems.length(); } else { displayItems = new ArrayNodeList(); nDisplayItems = 0; } int nValues; if ((nSaveItems != 0 && nDisplayItems != 0) && (nSaveItems != nDisplayItems)) { // bad user! nValues = Math.min(nSaveItems, nDisplayItems); } else { nValues = nSaveItems; } if (nValues == 0) continue; // create under the field data group Element items = (Element) mDataModel.createNode(XFA.DATAGROUPTAG, newDataNode, "items", "", true); // get save/display values String sDisplayValue = ""; String sSaveValue = ""; TextValue textNode; // add save/display values to for (int k = 0; k < nValues; k++) { if (saveItems.item(k) instanceof TextNode) { textNode = (TextValue)saveItems.item(k); sSaveValue = textNode.getValue(); } if (displayItems.item(k) instanceof TextNode) { textNode = (TextValue)displayItems.item(k); sDisplayValue = textNode.getValue(); } DataNode save = (DataNode)mDataModel.createNode(XFA.DATAVALUETAG, items, "save", "", true); ((DataNode)save).setValue(sSaveValue, true); DataNode display = (DataNode)mDataModel.createNode(XFA.DATAVALUETAG, items, "display", "", true); ((DataNode)display).setValue(sDisplayValue, true); }// endfor }// endif choiceList } }// endif ui } else if (formChild instanceof FormExclGroup) { FormExclGroup group = ((FormExclGroup)formChild); // add node newDataNode = (Element) mDataModel.createNode(XFA.DATAGROUPTAG, dataParent, aChildName, "", true); // add the raw value String sRawValue = group.getRawValue(); DataNode rawValue = (DataNode)mDataModel.createNode(XFA.DATAVALUETAG, newDataNode, "value", "", true); rawValue.setValue(sRawValue, true); } else if (formChild instanceof FormSubform) { if (aChildName == "") { newDataNode = dataParent; } else { // add node, and recurse newDataNode = (Element) mDataModel.createNode(XFA.DATAGROUPTAG, dataParent, aChildName, "", true); } createOverlayData(formChild, newDataNode); } } } /** * Notifies the FormModel that a new page is being created so that it can take * appropriate action. * * @param page * the template pageArea node * * TODO: Change return type to FormPageArea. * * @exclude from published api. */ PageArea createPage(PageArea page) { assert (mCurrentPageSet != null); return (PageArea)importNode(page, mCurrentPageSet, true); } /** * Notifies the FormModel that a new page is being created so that it can * take appropriate action. * * @param oPage - * the xfatemplate node * @return Imported copy of given pageArea that resides in a pageset under * the root subform. * * TODO: Change return type to FormPageArea. * * @exclude from published api. */ PageSet createPageSet(PageSet pageSet) { Node pageSetParent = pageSet.getXFAParent(); Element parent = mCurrentPageSet; // first page set added, use the root subform as the parent of the pageset if (parent == null) parent = mRootFormSubform; // find the proper level to create the new pageSet. while (parent instanceof PageSet) { // found common parent if (((PageSet)parent).getProto() == pageSetParent) break; // move up our pagesets parent = parent.getXFAParent(); } assert(parent.isSameClass(pageSetParent)); mCurrentPageSet = createLayoutNode(pageSet, parent); return (PageSet)mCurrentPageSet; // JavaPort: this looks questionable } /** * Creates an overlayData section under dataSets. * This method is for Form Server support. * * @param nPanel * the panel to create the overlayData section for * @exclude from published api. */ public void createPanelOverlayData(int nPanel) { // THIS IS A HACK FOR FORM SERVER!!!!!!!!!!!! // If the output type is mergedXDP and the destination is webClient, // output overlay data for the second level subform for caching // purposes. // find the second level subform that is the currentPage Node topSubform = getFirstXFAChild(); Node targetSubform = null; int nCnt = 0; for (Node child = topSubform.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (child instanceof FormSubform) { if (nCnt == nPanel) { targetSubform = child; break; } nCnt++; } } if (targetSubform != null) { createOverlayData(targetSubform); } } private void doBindItems(Element bindItems, FormField field, String sRequestConnectionName) { String sConnection = ""; Attribute connection = bindItems.getAttribute(XFA.CONNECTIONTAG, true, false); if (connection != null) sConnection = connection.toString(); String sRef = ""; Attribute ref = bindItems.getAttribute(XFA.REFTAG, true, false); if (ref != null) sRef = ref.toString(); if (sRef.length() != 0 && sConnection.length() != 0) { // it's a wsdl bindItems if (sRequestConnectionName.equals(sConnection)) { // looking for wsdl // if it's a wsdl data merge then the connection data is transient so there's no // point listening to it. field.setItemsDataListener(null); field.updateItemsFromData(bindItems, true); } } else if (sRef.length() == 0|| sConnection.length() == 0) { // default data if sRef or database(sourceset) if sConnection if (sRequestConnectionName.length() == 0) { // not looking for wsdl FormItemsDataListener listener = new FormItemsDataListener(field, bindItems); field.setItemsDataListener(listener); field.updateItemsFromData(bindItems, false); } } } /** * Load a node from one DOM into another. * In the C++ implementation, this would load a node from the XML DOM into the XFA DOM. * In this implementation, this method exists to handle the case of a form packet used * to restore state that has been parsed into a generic packet, but now needs to be loaded * into an XFA FormModel DOM. * @param parent the XFA Form parent of the node that is to be loaded * @param node the generic packet node that is to be loaded * @param genTag * @return the loaded node * @exclude from published api. */ protected Node doLoadNode(Element parent, Node node, Generator genTag) { assert node instanceof Element || node instanceof Chars; // Check if we are dealing with ExData. if (parent instanceof ExDataValue && node instanceof Element) { boolean bCreate = false; int eNewNodeTag = XFA.RICHTEXTNODETAG; // Check the namespace of the child element to see if it's equal // to the xhtml namespace. if (((Element)node).getNS() == STRS.XHTMLNS) { bCreate = true; } else { // watson bug 1856188, since the

    packet doesn't // have all the ui info we don't know the type of the field so we must // inspect the content type of String sContent = parent.getAttribute(XFA.CONTENTTYPETAG).toString(); if (sContent.equals(STRS.TEXTXML)) { eNewNodeTag = XFA.XMLMULTISELECTNODETAG; bCreate = true; } } if (bCreate) { // JavaPort: In the C++, the XML or rich text would be left in the XML DOM // and simply peered to a new XFA node, but in XFA4J we need to // copy and import the nodes into this XFA DOM. return importContent(parent, node, eNewNodeTag); } MsgFormatPos msg = new MsgFormatPos(ResId.InvalidNodeTypeException, ((Element)node).getLocalName()); addErrorList(new ExFull(msg), LogMessage.MSG_WARNING, null); return null; } else { return super.doLoadNode(parent, node, genTag); } } /** * Imports generic nodes representing rich text or XML into this FormModel. */ private Node importContent(Element parent, Node node, int eType) { if (node instanceof Chars) { return new TextNode(parent, null, ((Chars)node).getText()); } else if (node instanceof Element) { Element element = (Element)node; Element newElement = null; if (eType == XFA.RICHTEXTNODETAG) newElement = new RichTextNode(parent, null); else if (eType == XFA.XMLMULTISELECTNODETAG) newElement = new XMLMultiSelectNode(parent, null); else assert false; newElement.setDOMProperties(element.getNS(), element.getLocalName(), element.getXMLName(), null); // TODO: Do we need to uniquify id attributes as for FormField#importRichTextContext? for (int i = 0; i < element.getNumAttrs(); i++) { Attribute attr = element.getAttr(i); newElement.setAttribute(attr.getNS(), attr.getName(), attr.getLocalName(), attr.getAttrValue(), false); } for (Node child = element.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { importContent(newElement, child, eType); } return newElement; } else if (node instanceof Comment) { return new Comment(parent, null, ((Comment)node).getData()); } else if (node instanceof ProcessingInstruction) { ProcessingInstruction pi = (ProcessingInstruction)node; return new ProcessingInstruction(parent, null, pi.getName(), pi.getData()); } else { MsgFormatPos msg = new MsgFormatPos(ResId.InvalidNodeTypeException, node.getClassName()); addErrorList(new ExFull(msg), LogMessage.MSG_WARNING, null); return null; } } private void doSetProperty(Element setProperty, Container container, boolean bIsConnectionBind) { Attribute ref = setProperty.getAttribute(XFA.REFTAG, true, false); Attribute target = setProperty.getAttribute(XFA.TARGETTAG, true, false); if (ref == null || target == null) return; String sRef = ref.toString(); String sTarget = target.toString(); if (sRef.length() == 0 || sTarget.length() == 0) return; Node dataNode = null; if (bIsConnectionBind) { Node node = container; while (node != null) { if (node instanceof Container && ((Container)node).getFormInfo() != null) dataNode = ((Container)node).getFormInfo().connectionDataNode; if (dataNode != null) break; node = node.getXFAParent(); } } else { dataNode = getDataNode(container); } // Could use resolveNode() here since we're only expecting one but it's awfully // easy to set up a ref which might inadvertantly find >1 some of the time. // So allow for the possibilty of more than one and use the first. // The alternative is to use resolveNode() and catch the exception that's // thrown but it doesn't seem like it's worth doing that. NodeList refNodes; if (dataNode != null) { refNodes = dataNode.resolveNodes(sRef, true, false, true); } else { // absolute ref? refNodes = mDataModel.resolveNodes(sRef, true, false, true); } Node refNode = null; if (refNodes.length() > 0) refNode = (Node)refNodes.item(0); if (refNode == null || !refNode.isSameClass(XFA.DATAVALUETAG)) return; String sSetValue = ((DataNode)refNode).getValue(); StringHolder sTargetProperty = new StringHolder(); Node targetNode = resolveSetPropertyTarget(container, sTarget, sTargetProperty); if (!(targetNode instanceof Element)) return; if (!isValidSetPropertyTarget(targetNode, container)) return; Element targetElement = (Element)targetNode; if (!StringUtils.isEmpty(sTargetProperty.value)) { targetElement.setProperty(new StringAttr(sTargetProperty.value, sSetValue), sTargetProperty.value); } else { if (targetNode instanceof TextValue) { ((TextValue)targetNode).setValue(sSetValue); } else if (targetElement.isPropertyValid(XFA.TEXTNODETAG)) { TextNode textNode = targetElement.getText(false, true, false); if (textNode != null) textNode.setValue(sSetValue, true, false); } } } /** * Enables or disables the incremental merge feature. * * @exclude from published api. */ void enableIncrementalMerge(boolean bEnableIncrementalMerge) { mbEnableIncrementalMerge = bEnableIncrementalMerge; } /** @exclude from published api. */ void enumerateScripts(List scripts, String sSingleLanguage /* = "" */) { // Use static method in TemplateModel. TemplateModel.enumerateScripts(this, this, scripts, sSingleLanguage); } private boolean legacyEventOccurred(EventManager em, int nEventId, int eReason, Element container, boolean recursiveCall) { // if you want to execute the calculate, validate or initialize on the whole form // container needs to be the root subform boolean bEventsDispatched = false; bEventsDispatched |= preExecEvent(em, nEventId, eReason, container, recursiveCall); bEventsDispatched |= execEvent(em, nEventId, eReason, container); bEventsDispatched |= postExecEvent(em, nEventId, eReason, container); return bEventsDispatched; } private boolean eventOccurred(EventManager em, int nEventId, int eReason, Element container, boolean recursiveCall /*Default = false*/) { if (mTemplateModel.getLegacySetting(AppModel.XFA_LEGACY_V32_SCRIPTING)) { return legacyEventOccurred(em, nEventId, eReason, container, recursiveCall); } EventInfo retrieve = null; try{ if(mEventPseudoModel != null) { retrieve = mEventPseudoModel.getEventInfo(); mEventPseudoModel.reset(); mEventPseudoModel.setTarget(container); mEventPseudoModel.setName(eReason); } return legacyEventOccurred (em, nEventId, eReason, container, recursiveCall); }finally{ if (mEventPseudoModel != null) mEventPseudoModel.setEventInfo(retrieve); } } /** * Notifies event listeners that an event has occurred on a container. This * method may fire additional events before and/or after firing the event. * * @param sActivity * the name of the event * @param container * the container on which the event occurs * @return true if one or more events were dispatched. */ public boolean eventOccurred(String sActivity, Obj container) { // if you want to execute the calculate, validate or initialize on the whole form // container needs to be the root subform int eReason = ScriptHandler.stringToExecuteReason(sActivity); EventManager em = getEventManager(); int nId = em.getEventID(sActivity); if (container instanceof Element) { Element node = (Element)container; if (node.getModel() == this) return eventOccurred(em, nId, eReason, node, false); } return em.eventOccurred(nId, container); } private boolean execEvent(EventManager em, int nEventId, int eReason, Node node) { if (eReason == ScriptHandler.ACTIVITY_INDEXCHANGE && !node.isSameClass(XFA.SUBFORMTAG) ) return false; // watson 1622409, protected objects don't fire user based events events. if (eReason > ScriptHandler.ObjectInteraction_Start && eReason < ScriptHandler.ObjectInteraction_End && mTemplateModel != null && !mTemplateModel.getLegacySetting(AppModel.XFA_LEGACY_V27_EVENTMODEL) && node instanceof Container && ((Container)node).isValidAttr(XFA.ACCESSTAG, false, null)) { // waton bug 1745666, use the runtime access setting Container container = (Container)node; int eAccess = container.getRuntimeAccess(EnumAttr.UNDEFINED); if (eAccess == EnumAttr.ACCESS_PROTECTED) return false; } // As of https://zerowing.corp.adobe.com/display/xtg/InactivePresenceXFAProposal, // event processing associated with inactive containers must not occur. // To mitigate performance on older forms, only do this check for XFA 3.0 documents and higher. if (mTemplateModel != null && mTemplateModel.getOriginalXFAVersion() >= Schema.XFAVERSION_30 && node instanceof Container) { Container container = (Container)node; int ePresence = container.getRuntimePresence(EnumAttr.UNDEFINED); if (EnumAttr.PRESENCE_INACTIVE == ePresence) return false; } return em.eventOccurred(nEventId, node); } // Adobe patent application tracking # P624, entitled "Form-based Data Storage And Retrieval", inventors: Matveief,Young,Solc /** * @exclude from published api. */ public void exportConnectionData (String strConnectionName, String sDataDescriptionName) { // got the form // // Init the data set at <"connectionName"> // Do this first so there's always a resultant data set even if it's // empty due to error. // AppModel appModel = (AppModel)getXFAParent(); mDataModel = DataModel.getDataModel(appModel, false, false); // // Init the !connectionData node with a child named with the connection name // DataNode connectionDataNode = (DataNode)resolveNode("!connectionData"); if (connectionDataNode == null) connectionDataNode = (DataNode)mDataModel.createChild(false, XFA.CONNECTIONDATA); Node old = connectionDataNode.resolveNode(strConnectionName, false, false, true); if (old != null) { // need to delete it. old.remove(); } DataNode exportDataRoot = new DataNode(connectionDataNode, null); String internedConnectionName = strConnectionName.intern(); exportDataRoot.setDOMProperties(null, internedConnectionName, internedConnectionName, null); // // got the DD name - now find the DD // Node dataDescription = null; // JavaPort: oDDNodes isn't referenced and the call to resolveNodes doesn't do anything useful //NodeList oDDNodes = mDataModel.resolveNodes(XFA.DATADESCRIPTION, false, false, false); for (Node dataModelChild = mDataModel.getFirstXFAChild(); dataModelChild != null; dataModelChild = dataModelChild.getNextXFASibling()) { if (dataModelChild instanceof Element) { Element dataModelChildElement = (Element)dataModelChild; if (dataModelChildElement.getNS() == STRS.DATADESCRIPTIONURI && dataModelChildElement.getName() == XFA.DATADESCRIPTION) { int index = dataModelChildElement.findAttr(STRS.DATADESCRIPTIONURI, XFA.NAME); if (index != -1) { if (dataModelChildElement.getAttrVal(index).equals(sDataDescriptionName)) { dataDescription = dataModelChild; break; } } } } } if (dataDescription == null) { return; } // check we have a data description with the correct root DataNode dataDescriptionRoot = (DataNode)dataDescription.resolveNode(strConnectionName, false, false, true); if (dataDescriptionRoot == null) { return; } exportDataRoot.setDataDescription(dataDescriptionRoot); mDataModel.initFromDataDescription(exportDataRoot); // // traverse the form looking for for this connection and export // recurseConnectOnNode(this, strConnectionName, EnumAttr.USAGE_EXPORTONLY, mConnectExportHandler, null); // cleanup leftover dd attributes DataModel.removeDDPlaceholderFlags(exportDataRoot, true); } /** * Searches for a match between the Containers in list (and their * descendents) and the children of dataParent. * * @param list * a list of Containers to search * @param dataParent * the parent of the DataNodes to search * @param connectionDataParent * @return the Container in list that was matched, or null if * no match was found. */ private Element findDescendantMatch(List list, Element dataParent, Element connectionDataParent) { // weight the data tree if needed if (!mbWeightedData && mStartNode != null) { mStartNode.setWeight(1); mbWeightedData = true; } Element targetNode = null; int nWeight = 0; int nCount = list.size(); for (int i = 0; i < nCount; i++) { Container templateNode = list.get(i); Container.FormInfo formInfo = getFormInfo(templateNode, dataParent, connectionDataParent); int nNewWeight = 0; DataNode dataNode = findMatch(formInfo, true, FormModel.DatasetSelector.MAIN_DATASET); if (dataNode != null) { nNewWeight = dataNode.getWeight(); } // We really only want to keep descending through MATCH_NONE nodes, but the old // algorithm didn't make that distinction. (Note: mbGlobalConsumption flag used // as a legacy flag here.) else if (formInfo.eMergeType == EnumAttr.MATCH_NONE || mbGlobalConsumption) { IntegerHolder newWeight = new IntegerHolder(nNewWeight); hasDescendantMatch(templateNode, dataParent, connectionDataParent, formInfo.eMergeType, false, newWeight); nNewWeight = newWeight.value; } // overwrite the weight and return node if it is less than the previous if (nNewWeight > 0) { if (nWeight == 0 || nNewWeight < nWeight) { nWeight = nNewWeight; targetNode = templateNode; } } } return targetNode; } /** * Finds a global data node. * used with resolveGlobal */ private Node findGlobalNode(Element templateNode, Element dataParent, IntegerHolder nCount, DataWindow dataWindow /* = null */ ) { // always return the last match Node match = null; for (Node dataChild = dataParent.getFirstXFAChild(); dataChild != null; dataChild = dataChild.getNextXFASibling()) { if (isMappable(templateNode, dataChild, true, false)) { nCount.value++; match = dataChild; } if (dataChild.getClassTag() == XFA.DATAGROUPTAG) { if (dataWindow != null) { if (dataWindow.isRecordGroup((DataNode)dataChild)) continue; } Node temp = findGlobalNode(templateNode, (DataNode)dataChild, nCount, dataWindow); if (temp != null) match = temp; } // use datawindow to check if we found the correct one. if (match != null) { // first instance in the record and the one that matches // the record count outside the record if (dataWindow == null) break; else if (dataWindow.recordAbsIndex(0) == nCount.value) break; } } return match; } /** * Searches the data model for a match with the given form node described. If * form node does not match any of the children continue searching * recusively up the data model if match = ONCE *

    * Will only look for a match, will never create one. bUseMainDataList==true * is the normal case, false is a secondary findMatch used during a * connection merge to ensure that form nodes created from connection data * are then bound to any pre-existing data nodes so that data from the * import replaces existing data. * * @param formInfo * contains data binding info for the form node * @param bUnMappedOnly * whether to match mapped or unmapped data nodes * @param bUseMainDataList * if true search formInfo.oDataNodes (main); * otherwise search formInfo.oAltDataNodes * @return the matched DataNode or null if no match is found * * @exclude from published api. */ DataNode findMatch(Container.FormInfo formInfo, boolean bUnMappedOnly, DatasetSelector eDataset /* = MAIN_DATASET */ ) { if (formInfo == null || formInfo.eMergeType == EnumAttr.MATCH_NONE) return null; if (formInfo.eMergeType == EnumAttr.MATCH_GLOBAL && bUnMappedOnly) return null; // globals don't cause new subforms to be created NodeList /* */ list = null; switch(eDataset) { case MAIN_DATASET: list = formInfo.dataNodes; break; case ALT_DATASET: list = formInfo.altDataNodes; break; default: assert(false); } Container templateNode = formInfo.templateContainerNode; while (list.length() > 0) { DataNode dataNode = (DataNode)list.item(0); if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) { // CONSUME_DATA must do its consumption here (based on the map flags). We have to do // it this way because descendant matches don't know how to update their ancestors' // formInfo, so when they steal nodes our of our formInfo we never hear about it. // if (formInfo.bRemoveAfterUse && dataNode.isMapped() || !isMappable(templateNode, dataNode, false, false)) { list.remove(dataNode); continue; } if ((bUnMappedOnly && !dataNode.isMapped()) || !bUnMappedOnly) return dataNode; else return null; } else if (mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) { // MATCH_TEMPLATE doesn't have the above problem as it doesn't do uncontrolled descendant // matching. It therefore already did all the consuming it needed to do directly in // ConsumeDataNode(). // return dataNode; } } // we didn't find a match, use scope matching if (formInfo.eMergeType == EnumAttr.MATCH_ONCE) { // update our list with new data list boolean bAdded = false; while (!bAdded && formInfo.scopeData != null) { Element parent = formInfo.scopeData.getXFAParent(); // watson bug 1229262 // we can not do scope matching outside the current dataset because if we do // and we modify the data structure we can pull in one dataset into another. if (mDataModel == parent) break; formInfo.scopeData = parent; if (formInfo.scopeData != null && formInfo.scopeData.getModel() == mDataModel) { if (getAssociation(parent, templateNode.getName(), list)) { // done, list populated by getAssociation } else { // collect all the mappable for (Node dataChild = formInfo.scopeData.getFirstXFAChild(); dataChild != null; dataChild = dataChild.getNextXFASibling()) { // check if the data node and proto node are mappable if (isMappable(templateNode, dataChild, true, true)) { // do not scope match a data group that is a record!!! if (dataChild.getClassTag() == XFA.DATAGROUPTAG) { DataWindow dataWindow = mDataModel.getDataWindow(); if (dataWindow != null) { if (dataWindow.isRecordGroup((DataNode)dataChild)) continue; } } list.append(dataChild); bAdded = true; } } } } // done scope matching else break; } if (bAdded) return findMatch(formInfo, bUnMappedOnly, eDataset); } return null; } // Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc private Node findUnMappedOverlayDataChild(Node dormNode, Node dataParent) { // THIS IS A HACK FOR FORM SERVER!!!!!!!!!!!! // fields map to dataGroups in overlay data // This is a hack for FormServer for (Node dataChild = dataParent.getFirstXFAChild(); dataChild != null; dataChild = dataChild.getNextXFASibling()) { // check if the data node and proto node are mappable if (isMappableForOverlayData(dormNode, dataChild, true)) return dataChild; } return null; } /** * Determines whether the next {@link #remerge()} operation will adjust the * structure of the DataModel to match the TemplateModel. The default value * is false. * * @return true if the next {@link #remerge()} operation will * adjust the structure of the DataModel to match the TemplateModel. * @see #remerge() * @see #setAdjustData(boolean) */ public boolean getAdjustData() { return mbAdjustData; } // Adobe patent application tracking # B136, entitled "Applying locale behaviors to regions of a form", inventors: Gavin McKenzie, Mike Tardif, John Brinkman" /** * Gets the ambient locale override for the top level subform. This will be * used when resolving locales for fields and subforms. If a field/subform * does not have a locale specified, then they will inherit it from their * ancestory hierarchy. * * @return the ambient locale (e.g. "en_US"). * @exclude from published api. */ public String getAmbientLocale() { return msLocale; } /** * @see Model#getBaseNS() * @exclude from published api. */ public String getBaseNS() { return STRS.XFAFORMNS; } /** @exclude from published api. */ ScriptInfo getCalculateInfo(ProtoableNode node) { assert node != null; Element calcProp = node.getElement(XFA.CALCULATETAG, true, 0, false, false); if (calcProp == null) return null; return getScriptInfo(node, calcProp); } /** * Determines if calculations are enabled. * * @return true if calculations are enabled. */ public boolean getCalculationsEnabled() { if (mbIgnoreCalcEnabledFlag) return true; if (mHostPseudoModel != null) return mHostPseudoModel.getCalculationsEnabled(); return true; } /** * Gets the current connection name * * @exclude from published api. */ String getConnectionName() { return msConnectionName; } // Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc /** * Gets the data ref for a node */ private String getDataRef(Element node, String sConnect, BooleanHolder bIsConnect /* = null */) { String sDataRef = ""; if (bIsConnect != null) bIsConnect.value = false; Element bind = node.getElement(XFA.BINDTAG, true, 0, false, false); // check to see if we have a bind tag if (bind != null) { sDataRef = bind.getAttribute(XFA.REFTAG).toString(); } // see if we have a data connection, if yes overwrite ref; if (!StringUtils.isEmpty(sConnect)) { StringHolder strConnectionRootRef = new StringHolder(); StringHolder strConnectRef = new StringHolder(); if (getConnectSOMStrings(node, msConnectionName, EnumAttr.USAGE_IMPORTONLY, strConnectionRootRef, strConnectRef, null)) { sDataRef = strConnectRef.value; // don't need to add the root ref since new files will have the root defined if necessary } if (bIsConnect != null && !StringUtils.isEmpty(strConnectRef.value)) bIsConnect.value = true; } return sDataRef; } /** * Gets the default Validate object. The default Validate object is used * whenever validation is initiated by some method that does not use a * Validate parameter, or when validation is initiated by * {@link #validate(Validate, Element, boolean, boolean)} or * {@link #recalculate(boolean, Validate, boolean)} and null * is passed to the validate parameter. For example, the default Validate * object is used when validation is initiated by the * execValidate scripting method of the FormModel. * * @return a reference to an object derived from Validate, or * null if no default Validate object has been set. * @see #setDefaultValidate(Validate) * @see #validate(Validate, Element, boolean, boolean) * @see #recalculate(boolean, Validate, boolean) */ public Validate getDefaultValidate() { return mDefaultValidate; } // Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman // Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman /** * @exclude from published api. */ public Obj getDelta(Element node, String sSOM) { Element delta = null; FormSubform deltaSubform = getDeltaSubform(); if (deltaSubform != null) { String sSOM2 = node.getSOMExpression(mRootFormSubform, false); // find the delta for this node using the som expression delta = (Element)deltaSubform.resolveNode(sSOM2, true, false, false); } return new Delta(node, delta, sSOM); } // Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman // Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman /** @exclude from published api. */ public Obj getDeltas(Element node) { // create a new list XFAList list = new XFAList(); FormSubform deltaSubform = getDeltaSubform(); if (deltaSubform != null) { String sSOM = node.getSOMExpression(mRootFormSubform, false); // find the delta for this node using the som expression Element delta = (Element)deltaSubform.resolveNode(sSOM, true, false, false); // go grab the deltas if (delta != null && delta.isSameClass(node)) { node.getDeltas(delta, list); } } return list; } /** * Gets the deltas subform. * * @exclude from published api. */ FormSubform getDeltaSubform() { return mDeltasSubform; } /** * Determines whether scope matching is disabled. * Used for dynamic merge in a pageArea, data description content under an explicit match. * * @exclude from published api. */ boolean getMatchDescendantsOnly() { return mbMatchDescendantsOnly; } /** * Determines if the next {@link #remerge()} operation will perform an empty * merge. The default value is false. * * @return true if the next {@link #remerge()} operation will * perform an empty merge. * @see #remerge() * @see #merge(boolean, boolean, boolean, boolean, boolean) * @see #setEmptyMerge(boolean) */ boolean getEmptyMerge() { return mbEmptyMerge; } /** * Gets the Execute associated with this FormModel. May be null. * * @return the Execute. * @exclude from published api. */ public Execute getExecute() { return mExecute; } // Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc /** @exclude from published api. */ ExecuteInfo getExecuteInfo(ProtoableNode node, Node eventNode) { Node executeNode = null; String sEventContext = "$"; if (eventNode.isSameClass(XFA.EVENTTAG)) { Element eventElement = (Element)eventNode; executeNode = eventElement.getOneOfChild(true, false); sEventContext = eventElement.getAttribute(XFA.REFTAG).toString(); } else return null; if (executeNode == null || !executeNode.isSameClass(XFA.EXECUTETAG)) return null; // Element executeElement = (Element)executeNode; // Attribute runAt = executeElement.getAttribute(XFA.RUNATTAG); // Attribute executeType = executeElement.getAttribute(XFA.EXECUTETYPETAG); // Attribute connectionAttr = executeElement.getAttribute(XFA.CONNECTIONTAG); return new ExecuteInfo( sEventContext, //connectionAttr.toString(), //((EnumValue)runAt).getInt(), //((EnumValue)executeType).getInt(), node); } /** * Gets the field that has focus for the FormModel. * * @return the field that has focus. * @exclude from published api - UI methods not relevant for server * implementations. */ public FormField getFocus() { return mActiveField; } // Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc /** * Update and returns the FormInfo for templateNode * * @exclude from published api. */ Container.FormInfo getFormInfo(Node templateNode, Element dataParent, Element connectionDataParent /* = null */) { if (templateNode == null) return null; // this could be expanded to support datavalues if subforms can merge against datavalues. if (dataParent != null && dataParent.getModel() != mDataModel) return null; if (templateNode instanceof Container) { Container container = (Container)templateNode; // See if we can use the existing formInfo Container.FormInfo formInfo = container.getFormInfo(); if (formInfo != null && formInfo.dataParent == dataParent && formInfo.connectionDataParent == connectionDataParent) { return formInfo; } // get the merge type BooleanHolder bAssociation = new BooleanHolder(false); boolean bConnectDataRef = false; int eMergeType = mergeType(container, msConnectionName, null); if (eMergeType == EnumAttr.MATCH_ONCE || eMergeType == EnumAttr.MATCH_DESCENDANT) { NodeList list = new ArrayNodeList(); if (dataParent != null) { // Note: associations only supported in the new (MATCH_TEMPLATE) algorithm. if (mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE && getAssociation(dataParent, container.getName(), list)) { bAssociation.value = true; // List populated by getAssociation() } else { // Generally speaking, multiple data matches will create multiple form nodes. // The CONSUME_DATA algorithm matches individual data nodes with form nodes by consuming // the data nodes as it goes. // The MATCH_TEMPLATE algorithm matches based on index (first data node with first // template node, etc.) boolean bMatchAll = true; int nTargetIndex = 0; if (mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE && templateNode.getSibling(1, true, false) != null) { bMatchAll = false; nTargetIndex = templateNode.getIndex(true); } // collect all the mappable int nCurrIndex = 0; for (Node dataChild = dataParent.getFirstXFAChild(); dataChild != null; dataChild = dataChild.getNextXFASibling()) { // If the template and data nodes are mappable then this is a candidate match. if (isMappable(container, dataChild, true, true)) { if (bMatchAll || nCurrIndex == nTargetIndex) list.append(dataChild); else nCurrIndex++; } } } } // Update the form info for the container (passing any connectionDataParent since // there might be hierarchies of subforms with connect bindings interspersed with // subforms without). setFormInfo(container, dataParent, list, bAssociation.value, eMergeType, true, bConnectDataRef, connectionDataParent, null); } else if (eMergeType == EnumAttr.MATCH_DATAREF) { BooleanHolder bConnectDataRef2 = new BooleanHolder(); String sSom = getDataRef(container, msConnectionName, bConnectDataRef2); boolean bSomIsAbsolute = !somIsRelative(sSom); if (formInfo != null && bSomIsAbsolute) { // If we have an absolute SOM then go ahead and reuse any existing formInfo as a // change in the dataParent won't affect resolution. return formInfo; } else { NodeList dataNodes = new ArrayNodeList(); Node resolveContext = null; if (bConnectDataRef2.value) resolveContext = connectionDataParent; else resolveContext = dataParent; // A CONSUME_DATA merge will resolve these in the second pass; a MATCH_TEMPLATE // merge uses a single pass so we need to do them here. if (resolveContext == null && bSomIsAbsolute && mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) resolveContext = mStartNode; if (resolveContext != null) dataNodes = resolveContext.resolveNodes(sSom, true, false, true, null, bAssociation); if (mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) { // CONSUME_DATA prunes unmappable nodes in findMatch(), but MATCH_TEMPLATE assumes // the nodeList is all good, so we need to prune them here. // for (int i = dataNodes.length() - 1; i >= 0; i--) { Node candidate = (Node)dataNodes.item(i); if (!isMappable(container, candidate, false, false)) dataNodes.remove(candidate); } } NodeList altDataNodes = new ArrayNodeList(); // if it was a connection som expresson also get the nomally bound nodes too. if (bConnectDataRef2.value) { int eDataMergeType = mergeType(container, "", null); if (eDataMergeType == EnumAttr.MATCH_ONCE || eDataMergeType == EnumAttr.MATCH_DESCENDANT) { if (dataParent != null) { // Note: associations only supported in the new (MATCH_TEMPLATE) algorithm. if (mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE && getAssociation(dataParent, container.getName(), altDataNodes)) { bAssociation.value = true; // List populated by getAssociation } else { // collect all the mappable for (Node dataChild = dataParent.getFirstXFAChild(); dataChild != null; dataChild = dataChild.getNextXFASibling()) { // check if the data node and proto node are mappable if (isMappable(container, dataChild, true, true)) altDataNodes.append(dataChild); } } } } } // update the form info for the container setFormInfo(container, dataParent, dataNodes, bAssociation.value, eMergeType, isSomMultiple(sSom), bConnectDataRef2.value, connectionDataParent, altDataNodes); } } else if (eMergeType == EnumAttr.MATCH_GLOBAL) { // don't replace global forminfo if (formInfo == null) { boolean bUseDV = useDV(templateNode); NodeList dataNodes = new ArrayNodeList(); Node dataNode = resolveGlobal(container, bUseDV); if (dataNode != null) dataNodes.append(dataNode); // update the form info for the container setFormInfo(container, dataParent, dataNodes, bAssociation.value, eMergeType, false, false, null, null); } } else if (eMergeType == EnumAttr.MATCH_NONE) { // just set up nodes and merge type. setFormInfo(container, dataParent, null, bAssociation.value, eMergeType, false, false, connectionDataParent, null); } Container.FormInfo ret = container.getFormInfo(); assert ret != null; return ret; } return null; } /** * Returns all the form nodes bound to a dataNode. * * @param dataNode * a DataNode * @return a list of form nodes that are bound to dataNode */ NodeList getFormNodes(Node dataNode) { NodeList ret = new ArrayNodeList(); if (dataNode != null) { int nPeer = 0; Peer peer = dataNode.getPeer(nPeer); while (peer != null) { if (peer instanceof FormDataListener) { Node formNode = ((FormDataListener)peer).getFormNode(); if (formNode != null) ret.append(formNode); } nPeer++; peer = dataNode.getPeer(nPeer); } } return ret; } /** * Determines if the formState packet has been removed. * * @return true if the formState packet has been removed */ public boolean getFormStateRemoved() { return mbFormStateRemoved; } /** * Gets the formStateUsage setting. * * @return true if formState must be maintained * * @exclude from published api. */ boolean getFormStateUsage() { return mbFormStateUsage; } /** * Gets the friendly name (to use in user-facing communication) for a form * node * * @param formNode * a FormSubform, FormField or * FormExclGroup to get a user-friendly name for. * @return a string containing a user-friendly name for the form node. */ public String getFriendlyName(Element formNode) { assert formNode instanceof FormSubform || formNode instanceof FormField || formNode instanceof FormExclGroup; String sReturnVal = ""; if (formNode instanceof FormSubform || formNode instanceof FormField || formNode instanceof FormExclGroup) { Element assist = null; int ePriority = EnumAttr.PRIORITY_CUSTOM; boolean bDisableSpeak = false; if (formNode.isPropertySpecified(XFA.ASSISTTAG, true, 0)) { assist = formNode.getElement(XFA.ASSISTTAG, true, 0, false, false); if (assist != null && assist.isPropertySpecified(XFA.SPEAKTAG, true, 0)) { Element speak = assist.peekElement(XFA.SPEAKTAG, false, 0); if (speak != null && speak.isPropertySpecified(XFA.DISABLETAG, true, 0)) { if (speak.getEnum(XFA.DISABLETAG) == EnumAttr.BOOL_TRUE) bDisableSpeak = true; } if (speak != null && speak.isPropertySpecified(XFA.PRIORITYTAG, true, 0)) { if (speak.isPropertySpecified(XFA.PRIORITYTAG, true, 0)) ePriority = speak.getEnum(XFA.PRIORITYTAG); } } } int[] props = new int[4]; if (ePriority == EnumAttr.PRIORITY_CUSTOM) { props[0] = XFA.SPEAKTAG; props[1] = XFA.TOOLTIPTAG; props[2] = XFA.CAPTIONTAG; props[3] = XFA.NAMETAG; } else if (ePriority == EnumAttr.PRIORITY_TOOLTIP) { props[0] = XFA.TOOLTIPTAG; props[1] = XFA.SPEAKTAG; props[2] = XFA.CAPTIONTAG; props[3] = XFA.NAMETAG; } else if (ePriority == EnumAttr.PRIORITY_CAPTION) { props[0] = XFA.CAPTIONTAG; props[1] = XFA.SPEAKTAG; props[2] = XFA.TOOLTIPTAG; props[3] = XFA.NAMETAG; } else { props[0] = XFA.NAMETAG; props[1] = XFA.SPEAKTAG; props[2] = XFA.TOOLTIPTAG; props[3] = XFA.CAPTIONTAG; } for (int nIndex = 0; nIndex < 4; nIndex++) { int nProp = props[nIndex]; if (nProp == XFA.SPEAKTAG && ! bDisableSpeak) { if (assist != null && assist.isPropertySpecified(XFA.SPEAKTAG, true, 0)) { ProtoableNode speak = (ProtoableNode)assist.peekElement(XFA.SPEAKTAG, false, 0); if (speak != null) { TextNode text = speak.getText(true, false, false); if (text != null) { sReturnVal = text.getValue(); break; } } } } else if (nProp == XFA.TOOLTIPTAG) { if (assist != null && assist.isPropertySpecified(XFA.TOOLTIPTAG, true, 0)) { ProtoableNode tooltip = (ProtoableNode)assist.peekElement(XFA.TOOLTIPTAG, false, 0); if (tooltip != null) { TextNode text = tooltip.getText(true, false, false); if (text != null) { sReturnVal = text.getValue(); break; } } } } else if (nProp == XFA.CAPTIONTAG) { Element caption = formNode.getElement(XFA.CAPTIONTAG, true, 0, false, false); if (caption != null) { Value value = (Value)caption.peekElement(XFA.VALUETAG, false, 0); if (value != null) { Content captionContent = (Content)value.getOneOfChild(true, false); if (captionContent != null) { if (captionContent.getClassTag() == XFA.EXDATATAG) { sReturnVal = ((ExDataValue)captionContent).getValue(false, false, false); break; } else { TextNode text = captionContent.getText(true, false, false); if (text != null) { sReturnVal = text.getValue(); break; } } } } } } else if (nProp == XFA.NAMETAG) { sReturnVal = formNode.getAttribute(XFA.NAMETAG).toString(); break; } } } return sReturnVal; } /** * @see Model#getHeadNS() * @exclude from published api. */ public String getHeadNS() { return STRS.XFAFORMNS_CURRENT; } /** * @exclude from published api. */ public HostPseudoModel getHost() { return this.mHostPseudoModel; } private int getOccurAttribute(Element templateNode, int eTag) { // default value int nValue = 1; // XFAF only supports occur on "page level subforms" if (mbIsXFAF) { if (!(templateNode instanceof Subform) || templateNode.getXFAParent() != mRootSubform ) return nValue; } // Fields always have an occurence of 1 else if (!(templateNode instanceof Subform) && !(templateNode instanceof SubformSet)) return nValue; Element occur = templateNode.getElement(XFA.OCCURTAG,true, 0, false, false); // subformsets or subforms if (occur != null) { // use default Int value = (Int)occur.getAttribute(eTag); nValue = value.getValue(); } return nValue; } /** * Gets the OverlayDataMerge setting. * * @return true if OverlayDataMergeUsage must occur * * @exclude from published api. */ public boolean getOverlayDataMergeUsage() { // JavaPort: in C++, this had a size_t& parameter which in Java is split out into getPanelToMergeAgainst() return mbOverlayDataMergeUsage; } /** * Gets the panel to merge against. * * @return the panel (second-level subform) to merge against * * @exclude from published api. */ public int getPanelToMergeAgainst() { return mnPanel; } /** * Returns a list of the panel subforms * * @param container * @param panelSFList * * @exclude from published api. */ void getPanelSubforms(Node container, NodeList panelSFList) { if (container != null) { // If it's a subform it must be the root subform if (container instanceof Subform == container.getXFAParent() instanceof FormModel) { for (Node child = container.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (child != null) { if (child instanceof Subform) { panelSFList.append(child); } else if (child instanceof SubformSet) { // recursively traverse the subformset getPanelSubforms(child, panelSFList); } } } } else assert false; } } /** * Returns a list of the panel subforms * * @param panelSFList * * @exclude from published api. */ void getPanelSubforms(NodeList panelSFList) { // Panel subforms are the subform children of the root subform where // subformsets are treated as transparent. for (Node topSubform = getFirstXFAChild(); topSubform != null; topSubform = topSubform.getNextXFASibling()) { if (topSubform instanceof Subform) { // add subform children, recursively traversing any // subformsets getPanelSubforms(topSubform, panelSFList); break; } } } /** * Gets the callback method that is to be invoked after merge but before any initialization * scripts are run. * * @see #setPostMergeHandler(PostMergeHandler, Object) */ public PostMergeHandler getPostMergeHandler() { return mPostMergeHandler; } /** * @see Model#getProtoList() * * @exclude from published api. */ public List getProtoList() { return mTemplateModel.getProtoList(); } /** * Gets the runAt property that specifies where scripts should be executed * (client, server or both). The default is both. * * @return one of: EnumAttr.RUNSCRIPTS_CLIENT, * EnumAttr.RUNSCRIPTS_SERVER, * EnumAttr.RUNSCRIPTS_BOTH or * EnumAttr.RUNSCRIPTS_NONE * @see #setRunScripts(int) */ public int getRunScripts() { return meRunAtSetting; } /** @exclude from published api. */ FormModel.ScriptInfo getScriptInfo(ProtoableNode node, Element eventNode) { Element scriptNode = null; if (eventNode == null) return null; String sEventContext = "$"; if (eventNode.isSameClass(XFA.EVENTTAG)) { scriptNode = (Element)eventNode.getOneOfChild(true, false); sEventContext = eventNode.getAttribute(XFA.REFTAG).toString(); } else scriptNode = eventNode.getElement(XFA.SCRIPTTAG, true, 0, false, false); if (scriptNode == null || !scriptNode.isSameClass(XFA.SCRIPTTAG)) return null; // See if the script is for us to execute -- accept either an // empty string or "XFA" in the "binding" tag. String sBinding = scriptNode.getAttribute(XFA.BINDINGTAG).toString(); if (!StringUtils.isEmpty(sBinding) && !sBinding.equals("XFA")) return null; Attribute oRunAt = scriptNode.getAttribute(XFA.RUNATTAG); int eRunAt = ((EnumValue)oRunAt).getInt(); String sScriptType = scriptNode.getAttribute(XFA.CONTENTTYPETAG).toString(); TextNode scriptText = scriptNode.getText(true, false, false); if (scriptText == null) return null; // get the script to run. String sScriptText = scriptText.getValue(); return new ScriptInfo(sScriptText, sScriptType, sEventContext, eRunAt, node); } /** * @see Model#getScriptTable() * @exclude from published api. */ public ScriptTable getScriptTable() { return FormModelScript.getScriptTable(); } /** * Gets the ServerExchange implementation associated with this FormModel. * * @return a ServerExchange implementation, or null if no * ServerExchange has been set. * @see #setServerExchange(ServerExchange) * @exclude from published api. */ public ServerExchange getServerExchange() { return mServerExchange; } /** * Gets the Submit implementation associated with this FormModel. * * @return a pointer to a class derived from Submit. * @exclude from published api. */ public Submit getSubmit() { return mSubmit; } private SubmitInfo getSubmitInfo(ProtoableNode node, Node eventNode) { Element submitNode = null; String sEventContext = "$"; if (eventNode.isSameClass(XFA.EVENTTAG)) { Element eventElement = (Element)eventNode; submitNode = (Element)eventElement.getOneOfChild(true, false); sEventContext = eventElement.getAttribute(XFA.REFTAG).toString(); } else return null; if (submitNode == null || !submitNode.isSameClass(XFA.SUBMITTAG)) return null; // Submit format. // int eFormat = submitNode.getEnum(XFA.FORMATTAG); // Attribute content = submitNode.getAttribute(XFA.XDPCONTENTTAG); // int eEmbedPDF = submitNode.getEnum(XFA.EMBEDPDFTAG); // Attribute targetAttr = submitNode.getAttribute(XFA.TARGETTAG); // Attribute textEncoding = submitNode.getAttribute(XFA.TEXTENCODINGTAG); return new SubmitInfo( // targetAttr.toString(), // EnumAttr.getString(eFormat), // textEncoding.toString(), // content.toString(), // eEmbedPDF == EnumAttr.BOOL_TRUE, sEventContext, node); } /** * Gets the submit URL override for the top level subform. This will be used * to determine if we are submitting to FormServer. * * @return String, the submit URL. * @exclude from published api. */ public String getSubmitURL() { return msSubmitURL; } /** * @exclude from published api. */ Validate getValidate() { return mValidate; } private ValidateInfo getValidateInfo(ProtoableNode node) { if (getOverlayDataMergeUsage()) { int nPanel = getPanelToMergeAgainst(); // // In panel mode we only validate the current panel that is being // submitted // if (node.isSameClass(XFA.FIELDTAG) || node.isSameClass(XFA.SUBFORMTAG) || node.isSameClass(XFA.EXCLGROUPTAG)) { Node topSubform = getFirstXFAChild(); boolean bIsTopSubform = (node == topSubform); if (!bIsTopSubform) { Element panelSubform = null; Element parent; if (node.isSameClass(XFA.SUBFORMTAG)) panelSubform = node; parent = node.getXFAParent(); while (parent != null && parent != topSubform) { if (parent.isSameClass(XFA.SUBFORMTAG)) panelSubform = parent; parent = parent.getXFAParent(); } if (panelSubform != null) { int nIndex = panelSubform.getClassIndex(); // // The parent panel is not the current panel, don't // validate // if (nIndex != nPanel) return null; } } } } Element validateNode = node.getElement(XFA.VALIDATETAG, true, 0, false, false); // Check if we're dealing with a barcode field. We will need to validate // the value even when there is no validate node present String sBarcodeType = ""; if (validateNode == null && node.isSameClass(XFA.FIELDTAG)) { Element ui = node.getElement(XFA.UITAG, true, 0, false, false); if (ui != null) { // get current ui Node currentUI = ui.getOneOfChild(true, false); if (currentUI != null) { if (currentUI.isSameClass(XFA.BARCODETAG)) { // get the barcode type at the same time so we don't // have to look it // up everytime we're going to validate it. Attribute attr = ((Element)currentUI).getAttribute(XFA.TYPETAG); if (attr != null) sBarcodeType = attr.toString(); } } } } if (validateNode == null && !StringUtils.isEmpty(sBarcodeType)) { // Got a barcode, set up the validation info return new ValidateInfo( null, null, //false, false, false, true, "$", sBarcodeType, EnumAttr.RUNAT_CLIENT, // Only validate on the client side node); } else if (validateNode == null) return null; Element scriptNode = validateNode.getElement(XFA.SCRIPTTAG, true, 0, false, false); Element pictureNode = validateNode.getElement(XFA.PICTURETAG, true, 0, false, false); boolean bNullTest = validateNode.getEnum(XFA.NULLTESTTAG) != EnumAttr.TEST_DISABLED; boolean bFormatTest = validateNode.getEnum(XFA.FORMATTESTTAG) != EnumAttr.TEST_DISABLED && pictureNode != null; //boolean bScriptTest = validateNode.getEnum(XFA.SCRIPTTESTTAG) != EnumAttr.TEST_DISABLED; String sScriptType = null; String sScriptText = null; int eRunAt = EnumAttr.RUNAT_BOTH; if (scriptNode != null && scriptNode.isSameClass(XFA.SCRIPTTAG)) { // See if the script is for us to execute -- accept either an // empty string or "XFA" in the "binding" tag. Attribute runAt = scriptNode.getAttribute(XFA.RUNATTAG); eRunAt = ((EnumValue)runAt).getInt(); sScriptType = scriptNode.getAttribute(XFA.CONTENTTYPETAG).toString(); String sBinding = scriptNode.getAttribute(XFA.BINDINGTAG).toString(); if (StringUtils.isEmpty(sBinding) || sBinding.equals("XFA")) { TextNode scriptTextNode = scriptNode.getText(true, false, false); if (scriptTextNode != null) { // get the script to run. sScriptText = scriptTextNode.getValue(); } } } if (sScriptText == null) if (!bNullTest && !bFormatTest) return null; return new ValidateInfo( sScriptText, sScriptType, //bNullTest, bFormatTest, bScriptTest, false, "$", null, eRunAt, node); } /** * Determines whether validations are enabled. * * @return true if validations are enabled. */ public boolean getValidationsEnabled() { if (mbIgnoreValidationsEnabledFlag) return true; if (mHostPseudoModel != null) return mHostPseudoModel.getValidationsEnabled(); return true; } // Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc /** * Determines if a descendant of formInfo.poTemplateNode has a match for one * of the children of formInfo.poDataParent. * * @param formInfo * the formInfo to search for a match * @param bConnectOnly * @param nWeight * Contains the weight of the lowest matched DataNode if a match is found. * If no match is found, contains 0. * * If null no weighting data is collected. * @return true if a match was found. */ private boolean hasDescendantMatch(Element templateNode, Element dataParent, Element connectionDataParent, int eMergeType, boolean bConnectOnly /* = FALSE */, IntegerHolder weight /* = NULL */) { if (templateNode instanceof Field || templateNode instanceof Draw || (templateNode instanceof ExclGroup && eMergeType != EnumAttr.MATCH_NONE)) return false; // don't filter on scopeless since we must support old forms generated by designer. if (eMergeType == EnumAttr.MATCH_GLOBAL || eMergeType == EnumAttr.MATCH_DATAREF) bConnectOnly = true; // we must look through some nodes for connect matches only if (bConnectOnly && StringUtils.isEmpty(msConnectionName)) return false; if (bConnectOnly) dataParent = null; boolean bMatchFound = false; for (Node templateChild = templateNode.getFirstXFAChild(); templateChild != null; templateChild = templateChild.getNextXFASibling()) { Container.FormInfo info = getFormInfo(templateChild, dataParent, connectionDataParent); if (info != null) { DataNode dataNode = findMatch(info, true, FormModel.DatasetSelector.MAIN_DATASET); if (dataNode != null && (!bConnectOnly || info.bConnectDataRef)) { if (weight != null) { int nNewWeight = dataNode.getWeight(); // overwrite the weight and return node if it is less // than the previous if (nNewWeight > 0) { if (weight.value == 0 || nNewWeight < weight.value) weight.value = nNewWeight; } } bMatchFound = true; } // We really only want to keep descending through MATCH_NONE nodes, but the old // algorithm didn't make that distinction. (Note: mbGlobalConsumption flag used // as a legacy flag here.) else if (info.eMergeType == EnumAttr.MATCH_NONE || mbGlobalConsumption) { bMatchFound |= hasDescendantMatch((Element)templateChild, dataParent, connectionDataParent, info.eMergeType, bConnectOnly, weight); } // If we're not looking for the node with the lowest weight then we can stop // immediately after finding the first one. if (weight == null && bMatchFound) break; } } return bMatchFound; } // Adobe patent application tracking # P624, entitled "Form-based Data Storage And Retrieval" inventors: Matveief,Young,Solc /** * @exclude from published api. */ public void importConnectionData (String strConnectionName) { BooleanHolder allowed = new BooleanHolder(true); // #ifdef ACROBAT_PLUGIN /* Watson bug 1368015 do a first pass to see if this will violate XFA perms.*/ recurseConnectOnNode(this, strConnectionName, EnumAttr.USAGE_IMPORTONLY, mConnectImportPermCheckHandler, allowed); // #endif if (allowed.value) { // Javaport: not used by handler! //List connectionDataNodes = new ArrayList(); // list of fields to try metaData bindings. recurseConnectOnNode(this, strConnectionName, EnumAttr.USAGE_IMPORTONLY, mConnectImportHandler, null); } else { MsgFormatPos message = new MsgFormatPos(ResId.PermissionsViolationExceptionMethod); message.format("execute"); throw new ExFull(message); } } /** * Imports a template node into this model, while resolving any global or * dataRef fields. This will simply walk the template DOM and create a form * node for each one it encounters. This will also set up the appropriate * proto relationships * * @param templateNode * the template node to import * @param formParent * the parent we'll add the created form node too * @param bFull * if true, do merge and process the children. * @return the newly created Form node * * @exclude from published api. */ public Element importNode(ProtoableNode templateNode, Element formParent /* = null */, boolean bFull /* = true */) { // allow new nodes to be created boolean bWasAllowingNewNodes = allowNewNodes(true); // store the old adjustData value so we can reset it boolean bOldAdjustData = mbAdjustData; // if we don't have a parent then we are peeking and don't want to add // any new data if (formParent == null) mbAdjustData = false; // create a form node from the proto ProtoableNode newFormNode = (ProtoableNode)createNode(templateNode.getClassTag(), formParent, "", "", true); // set the name of the new node String aNodeName = templateNode.getName(); if (aNodeName != null && "" != aNodeName) newFormNode.privateSetName(aNodeName); outputTraceMessage(ResId.NodeCreatedTrace, newFormNode, null, ""); // set up proto relationship newFormNode.setProto(templateNode); if (bFull) { Node mappedParent = getMappedParent(newFormNode); DataNode dataParent = null; if (mappedParent != null) dataParent = getDataNode(mappedParent); if (dataParent == null) dataParent = mDataModel.getDataRoot(); // For each child of template prototype node for (Node templateChild = templateNode.getFirstXFAChild(); templateChild != null; templateChild = templateChild.getNextXFASibling()) { if (!(templateChild instanceof Element)) continue; Element templateChildElement = (Element)templateChild; FormInstanceManager newInstanceManager = createInstanceManager(templateChildElement, newFormNode); if (mbEmptyMerge) { createEmptyFormNode(templateChildElement, newFormNode, null, newInstanceManager); } else { createAndMatchNode(templateChildElement, dataParent, newFormNode, null); } } // since we do scope matching now we need to a full post merge mergeSecondPass(newFormNode, dataParent); // Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman // Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman // restore values and instance counts. // restore the state only if the root subform is set to restoreState = auto // see http://xtg.can.adobe.com/twiki/bin/view/XFA/FormStateXFAProposal FormSubform deltaSubform = getDeltaSubform(); if (deltaSubform != null) { // turn on is loading, so that we can restore values and instance counts isLoading(true); try { // turn on is loading so that instance counts and default values are restored String sSOM = newFormNode.getSOMExpression(mRootFormSubform, false); // find the delta for this node using the som expression Element delta = (Element)deltaSubform.resolveNode(sSOM, true, false, false); XFAList list = new XFAList(); if (delta != null && delta.isSameClass(newFormNode)) { if (mbRestoreDeltas) newFormNode.getDeltas(delta, list); else newFormNode.getDeltas(delta, null); // restore the deltas in oList int nLen = list.length(); for (int i = 0; i < nLen; i++) { Delta deltaNode = (Delta)list.item(i); deltaNode.restore(); } } } finally { // turn off the loading flag isLoading(false); } } // set dynamic properties - follows mergeSecondPass as in normal merge case if (newFormNode instanceof Container) { // Note: passing empty string for connection name here which means wsdl dynamic bindings // won't work in the master page - however - it appears wsdl data binding in general // does not and never has worked in master pages - with no known customer complaints. // So postpone fixing all wsdl binding functionality until after 7.0.5/7.1 // WSDL issue logged/deferred in Watson 1224853 --jak setDynamicProperties((Container)newFormNode, "", true); } } // reset adjust data mbAdjustData = bOldAdjustData; allowNewNodes(bWasAllowingNewNodes); return newFormNode; } /** * Performs a incremental merge using the options from the last merge. If * unable to complete the merge, returns false (in which case a full merge * needs to be done). */ private boolean incrementalMerge() { if (mDataModel == null || mRootSubform == null) return false; // No previous merge; do full merge. if (mDataDescription == null) return false; if (!incrementalMergeCheckDataDescription(mDataDescription)) return false; DataWindow dataWindow = mDataModel.getDataWindow(); if (dataWindow == null || ! dataWindow.isDefined()) return false; // Retrieve the previous record (which is peered to our // root subform). DataNode previousRecord = getDataNode(mRootFormSubform); // grab the (new) current record DataNode newRecord = dataWindow.record(0); if (previousRecord == newRecord) return false; // We're on the same record as before! return incrementalMergeUpdateTree(previousRecord, newRecord); } /** * Incremental merge. This function walks two data records (the previous and the current, new one). * The form-DOM pointers to the old data record are updated to point to the new data record. * Returns false if a situation arises that it can't handle (in which case a full merge must be done). */ @FindBugsSuppress(code="ES") private boolean incrementalMergeUpdateTree(DataNode prevDataNode, DataNode newDataNode) { // Check to see if we can incrementally update this subtree. If not, return false. if ( !prevDataNode.isSameClass(newDataNode)) return false; if (prevDataNode.getName() != newDataNode.getName()) return false; // Hunt for the XFAFormDataListener peer, disconnect it from the previous // data node, and connect it to the new data node. int nPeer = 0; Peer peer = prevDataNode.getPeer(nPeer); while (peer != null) { if (peer instanceof FormDataListener) { FormDataListener listener = ((FormDataListener)peer); Node formNode = listener.getFormNode(); assert newDataNode != listener.getDataNode(); if (formNode != null) { // Break connection to previous record, and reconnect // current record to this form field's listener. listener.setDataNode(newDataNode); if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) newDataNode.setMapped(true); } } nPeer++; peer = prevDataNode.getPeer(nPeer); } // Recursively apply incrementalMergeUpdateTree to children. int nPrevDataChildren = dataNodeChildrenCount(prevDataNode); int nNewDataChildren = dataNodeChildrenCount(newDataNode); if (nNewDataChildren > nPrevDataChildren) { // New current record has more children than the previous. // Can't handle this case yet. return false; } List prevDataChildren = dataNodeChildren(prevDataNode); List newDataChildren = dataNodeChildren(newDataNode); for (int nPrev = 0, nNew = 0; nPrev < nPrevDataChildren; nPrev++, nNew++) { DataNode prevDataChild = prevDataChildren.get(nPrev); if (nPrev == nNewDataChildren) { // Previous record has more children than the current record. // We can copy data values (usually calculated values) to the // current record. We can't copy data groups as they represent // a subform. Later, we'll handle this case using instance managers, // but for now fail out and do a full merge. if (prevDataChild.getClassTag() != XFA.DATAVALUETAG) return false; DataNode prevDataNodeChild = (DataNode)prevDataChild; // Copy or move the next data value to the current record. if (prevDataChild.isDefault(false)) { // Move the extra data value from the old record to the current. // Transient children would be deleted anyway. newDataChildren.add(prevDataChild); // Child was moved, so adjust variables. nPrev--; nPrevDataChildren--; } else { // Copy in the extra data value from the old record to the current. newDataChildren.add((DataNode)prevDataNodeChild.clone(newDataNode, true)); nNewDataChildren++; assert(nNewDataChildren == nPrev+1); } } DataNode newDataChild = newDataChildren.get(nNew); if ( ! incrementalMergeUpdateTree(prevDataChild, newDataChild)) return false; // Reset nPrevDataChildren, as a child of prevDataNode may have // been deleted. nPrevDataChildren = prevDataNode.getXFAChildCount(); } return true; } /** * Gets the number of DataNode children for a Node. * * @param node * the Node to be searched. * @return the number of children of node that are of class DataNode. */ private int dataNodeChildrenCount(Node node) { int count = 0; for (Node child = node.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) if (child instanceof DataNode) count++; return count; } /** * Gets a list of the DataNode children for a Node. * * @param node * the Node to be searched. * @return a list of the children of node that are of class DataNode. */ private List dataNodeChildren(Node node) { List nodeList = new ArrayList(); for (Node child = node.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) if (child instanceof DataNode) nodeList.add((DataNode)child); return nodeList; } /** * Fires the initialize event on all containers on the form, and fires the * indexChange event on all subforms. * * @return true if one or more events were dispatched. */ public boolean initialize() { boolean bRet = false; // Traverse and initialize all fields and subforms in the form if (mRootFormSubform != null) { String sInitialize = EnumAttr.getString(EnumAttr.ACTIVITY_INITIALIZE); EventManager em = getEventManager(); int nID = em.getEventID(sInitialize); bRet = eventOccurred(em, nID, ScriptHandler.ACTIVITY_INITIALIZE, mRootFormSubform, false); // fire index change on all subforms try { mbRecursiveIndexChange = true; // fire indexChange event String sIndexChange = EnumAttr.getString(EnumAttr.ACTIVITY_INDEXCHANGE); nID = em.getEventID(sIndexChange); bRet |= eventOccurred(em, nID, ScriptHandler.ACTIVITY_INDEXCHANGE, mRootFormSubform, false); } finally { mbRecursiveIndexChange = false; } } return bRet; } /** * Initializes the new content nodes * * @return true if one or more events were dispatched. * * @exclude from published api - used by layout. */ public boolean initializeNewContentNodes() { // Traverse and initialize all new layout fields and subforms boolean bEventsDispatched = false; String sInitialize = EnumAttr.getString(EnumAttr.ACTIVITY_INITIALIZE); EventManager em = getEventManager(); int nInitID = em.getEventID(sInitialize); int nIndexChangeID = em.getEventID(sInitialize); for (int i = 0; i < mLayoutContent.size(); i++) { LayoutContentInfo layoutContentInfo = (LayoutContentInfo)mLayoutContent.get(i); if (!layoutContentInfo.mbInitializeOccurred) { layoutContentInfo.mbInitializeOccurred = true; bEventsDispatched |= eventOccurred(em, nInitID, ScriptHandler.ACTIVITY_INITIALIZE, layoutContentInfo.mNode, false); // fire index changed on all subforms try { mbRecursiveIndexChange = true; // fire indexChange event bEventsDispatched |= eventOccurred(em, nIndexChangeID, ScriptHandler.ACTIVITY_INDEXCHANGE, layoutContentInfo.mNode, false); } finally { mbRecursiveIndexChange = false; } } } return bEventsDispatched; } /** * Checks if the given activity is excluded from execution. * * @exclude from published api. */ boolean isActivityExcluded(String activity) { if (mExcludeList != null) { for (int i = 0; i < mExcludeList.length; i++) { if (mExcludeList[i].equals(activity)) { return true; } } } return false; } /** * @see Model#isCompatibleNS(String) * @exclude from published api. */ public boolean isCompatibleNS(String aNS) { return Model.checkforCompatibleNS (aNS, STRS.XFATEMPLATENS) || Model.checkforCompatibleNS (aNS, STRS.XFAFORMNS); } /** * Checks if a data node is mappable with a form node, i.e. data groups map * to FormSubform and data values map to FormField and that the names and * match types are correct * * @param formNode * The form node to check * @param dataNode * The data node to check * @param bCheckNames * If true check the names * @param bUnMapped * If true, ensure the datanode is unmapped * @return true if the nodes are mappable */ @FindBugsSuppress(code="ES") private static boolean isMappable(Element formNode, Node dataNode, boolean bCheckNames /* = true */, boolean bUnMapped /* = true */) { // [Form]Subform elements map to DataNode elements // [Form]Field elements map to DataValue elements outputTraceMessage(ResId.NodeComparedTrace, formNode, dataNode, ""); // only if we are consuming data do we look at the bUnMapped flag if (bUnMapped && dataNode.isMapped()) return false; if (bCheckNames) { if (formNode.getName() == "") return false; if (formNode.getName() != dataNode.getName()) return false; } if (formNode instanceof Field && dataNode.getClassTag() != XFA.DATAVALUETAG) { boolean bIsMultiSelect = false; Element ui = ((Field)formNode).getElement(XFA.UITAG, true, 0, false, false); if (ui != null) { Node currentUI = ui.getOneOfChild(true, false); if (currentUI != null && currentUI.getClassAtom() == XFA.CHOICELIST) { if (((Element)currentUI).getEnum(XFA.OPENTAG) == EnumAttr.MULTISELECT) { bIsMultiSelect = true; } } } if (!bIsMultiSelect) return false; if (bIsMultiSelect && dataNode.getClassTag() != XFA.DATAGROUPTAG) return false; } if (formNode instanceof Subform && dataNode.getClassTag() != XFA.DATAGROUPTAG) return false; return true; } @FindBugsSuppress(code="ES") private static boolean isMappableForOverlayData(Node formNode, Node dataNode, boolean bUnMapped /* = true */) { // THIS IS A HACK FOR FORM SERVER!!!!!!!!!!!! // fields map to dataGroups in overlay data if (bUnMapped && dataNode.isMapped()) return false; if (formNode.getName() == "") return false; if (formNode.getName() != dataNode.getName()) return false; // Note - this differs from XFAFormModelImpl.isMappable // because field overlay data looks like this: // Hello // so fields are allowed to map to data groups if (formNode instanceof Subform && dataNode.getClassTag() != XFA.DATAGROUPTAG) return false; return true; } /** * Determines whether target is a valid target for setProperty. * {@link #setProperty(Object, int)} may only target properties of the * current container node. * * @param target * the property that is to be set. * @param container * the container node to be examined to see if it contains * target. * @return true of container is an ancestor of target. * * @see #setProperty(Object, int) * @see #setProperty(Object, String) */ private static boolean isValidSetPropertyTarget(Node target, Container container) { if (target == null) return false; for (Node parent = target; parent != null; parent = parent.getXFAParent()) { if (parent instanceof Container) { if (parent == container) return true; } } return false; } // Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman // Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman private void loadDeltas() { // ensure the correct setting mbRestoreDeltas // watson 1399480 don't check the restoreState attr if we are forcing the state to be restored if (!mbForceRestore) mbRestoreDeltas = mbRestoreDeltas && mRootSubform.getEnum(XFA.RESTORESTATETAG) == EnumAttr.RESTORESTATE_AUTO; AppModel appModel = getAppModel(); Packet formNode = null; for (Node child = appModel.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (child.getName() == XFA.FORM && child.isSameClass(XFA.PACKETTAG)) formNode = (Packet)child; } if (formNode == null) return; // remove it from the appmodel formNode.remove(); String sCheckSum = formNode.getAttribute(XFA.CHECKSUM); // the input stream doesn't have a checksum so don't load it. if (StringUtils.isEmpty(sCheckSum)) return; // ensure the checksum is valid if (!computeCheckSum().equals(sCheckSum)) return; // Get the subform child. Node subformChild = formNode.getFirstXMLChild(); while (subformChild != null) { if (subformChild instanceof Element && ((Element)subformChild).getLocalName() == XFA.SUBFORM) break; subformChild = subformChild.getNextXMLSibling(); } // didn't find the root subform if (subformChild == null) return; mDeltasSubform = new FormSubform(null, null); mDeltasSubform.setModel(this); mDeltasSubform.setNS(((Element)subformChild).getNS()); doLoadAttributes((Element)subformChild, mDeltasSubform); // Watson 1659331. Turn on is loading, so that notifyPeers implementations // don't attempt to do anything. isLoading(true); Generator genTag = new Generator("", ""); for (Node child = subformChild.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { if (child instanceof Element || child instanceof Chars) doLoadNode(mDeltasSubform, child, genTag); } isLoading(false); } /** @exclude from published api. */ protected void loadXMLImpl(Element parent, InputStream is, boolean bIgnoreAggregatingTag, ReplaceContent eReplaceContent) { // "%1 is an unsupported operation for the %2 object%3" // Note: %3 is extra text if necessary MsgFormatPos oMessage = new MsgFormatPos(ResId.UnsupportedOperationException); oMessage.format("loadXML"); oMessage.format(parent.getClassAtom()); throw new ExFull(oMessage); } /** * Check if templateNode can be a child of the formParent. * * @exclude from published api. */ boolean mapChild(Element templateNode, Element formParent) { // We're not interested in adding children. // or if the form parent is null // Watson bug 1347645, do not copy over pageset or pageArea nodes // this should only be done by layout. // XFAF doesn't support subformsets if (templateNode.isSameClass(XFA.PROTOTAG) || templateNode.isSameClass(XFA.PAGESETTAG) || templateNode.isSameClass(XFA.PAGEAREATAG) || formParent == null || (mbIsXFAF && templateNode.isSameClass(XFA.SUBFORMSETTAG)) ) { return false; } ChildReln reln = formParent.getChildReln(templateNode.getClassTag()); // If a proto child may appear [0..n] or [1..n] times, we need to // create a reference object so that the child will show up in our // own child list. if (reln.getMax() == ChildReln.UNLIMITED) { return true; } // property so not map. return false; } /** * Creates an ordered SubformSet. */ private int mapOrderedSubformSet(Element templateNode, Element formParent, DataNode dataParent, Element connectionDataParent, FormInstanceManager instanceManager) { // ensure we can create the node int nMax = getOccurAttribute(templateNode, XFA.MAXTAG); if ((nMax != ChildReln.UNLIMITED) && (nMax < 1)) return 0; int nRequired = getOccurAttribute(templateNode, XFA.MINTAG); int nCreated = 0; while (hasDescendantMatch(templateNode, dataParent, connectionDataParent, mergeType(templateNode, "", null), false, null)) { // Found a match! nCreated++; Element formNode = createFormNode(templateNode, formParent, instanceManager); createAndMatchChildren(templateNode, dataParent, formNode, connectionDataParent); // no data left or all subform sets matched. if ((nMax != ChildReln.UNLIMITED) && (nCreated == nMax)) { // max number of objects reached stop merging break; } } if (nCreated == 0) nRequired = getOccurAttribute(templateNode, XFA.INITIALTAG); // remove the number of matched from the required nRequired = nRequired - nCreated; // create the remaining required subformsets. for (int nCount = 0; nCount < nRequired; nCount++) { // A CONSUME_DATA merge uses a second pass for data creation so all we need to do is create // empty form nodes here. // A MATCH_TEMPLATE merge does everything in a single pass, so we must create the form node // and then process our children. // if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) createEmptyFormNode(templateNode, formParent, connectionDataParent, instanceManager); else { Element formNode = createFormNode(templateNode, formParent, instanceManager); createAndMatchChildren(templateNode, dataParent, formNode, connectionDataParent); } nCreated++; } return nCreated; } /** * Creates a SubformSet */ private int mapSubformSet(Element templateNode, Element formParent, DataNode dataParent, Element connectionDataParent) { // We do an explict descent in the new merge algorithm -- we no longer allow siblings, etc. to // influence the determinition of which subforms to instantiate. // Sadly, the old behavior (sub-optimal as it is) must be protected with a legacy flag -- for // which we're using mbGlobalConsumption. boolean bSaveMatchDescendantsOnly = mbMatchDescendantsOnly; if (!mbGlobalConsumption) mbMatchDescendantsOnly = true; int nCreated = 0; int eRelation = ((EnumValue)templateNode.getAttribute(XFA.RELATIONTAG)).getInt(); // create instanceManager for this subformset FormInstanceManager instanceManager = createInstanceManager(templateNode, formParent); if (eRelation == EnumAttr.RELATION_ORDERED) { nCreated = mapOrderedSubformSet(templateNode, formParent, dataParent, connectionDataParent, instanceManager); } else if (eRelation == EnumAttr.RELATION_CHOICE) { nCreated = mapUnorderedSubformSet(templateNode, formParent, dataParent, connectionDataParent, instanceManager, true); } else { // EnumAttr.RELATION_UNORDERED nCreated = mapUnorderedSubformSet(templateNode, formParent, dataParent, connectionDataParent, instanceManager, false); } mbMatchDescendantsOnly = bSaveMatchDescendantsOnly; return nCreated; } /** * Create a unordered SubformSet (choice or unordered) */ private int mapUnorderedSubformSet(Element templateNode, Element formParent, DataNode dataParent, Element connectionDataParent, FormInstanceManager instanceManager, boolean bIsChoice) { // ensure we can create the node int nMax = getOccurAttribute(templateNode, XFA.MAXTAG); if ((nMax != ChildReln.UNLIMITED) && (nMax < 1)) return 0; int nRequired = getOccurAttribute(templateNode, XFA.MINTAG); int nCreated = 0; // add subform and subformsets to unusedChildren. List unusedChildren = new ArrayList(); for (Node templateChild = templateNode.getFirstXFAChild(); templateChild != null; templateChild = templateChild.getNextXFASibling()) { if (templateChild instanceof Subform || templateChild instanceof SubformSet) { unusedChildren.add((Container)templateChild); } } // Process children of subformset Element targetProto = findDescendantMatch(unusedChildren, dataParent, connectionDataParent); while (targetProto != null) { // Found a match! nCreated++; // Create the subformset Element subformSet = createFormNode(templateNode, formParent, instanceManager); // create target createAndMatchNode(targetProto, dataParent, subformSet, connectionDataParent); unusedChildren.remove(targetProto); if (!bIsChoice && unusedChildren.size() > 0) { // match the other elements in the subform set based on the data order while (unusedChildren.size() > 0) { targetProto = findDescendantMatch(unusedChildren, dataParent, connectionDataParent); if (targetProto == null) break; createAndMatchNode(targetProto, dataParent, subformSet, connectionDataParent); unusedChildren.remove(targetProto); } // create empty elements for the remainder elements in the subformset for (int i = 0; i < unusedChildren.size(); i++) { Element subChild = unusedChildren.get(i); // A CONSUME_DATA merge uses a second pass for data creation, so all we need here is to // create the required number of empty form nodes. // A MATCH_TEMPLATE merge does everything in a single pass, which we simply delegate to // createAndMatchNode(). // if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) { // create instanceManager for this subformset createInstanceManager(subChild, subformSet); // XFA Template DOM node has no corresponding XFA Data DOM // nodes that match anything in the SubformSet. // Create the "initial" number an empty form nodes specified // in the occur element int nRequired2 = getOccurAttribute(subChild, XFA.MINTAG); // create the remaining required nodes. for (int nCount = 0; nCount < nRequired2; nCount++) createEmptyFormNode(subChild, subformSet, connectionDataParent, instanceManager); } else { createAndMatchNode(subChild, dataParent, subformSet, connectionDataParent); } // add to the used list unusedChildren.remove(targetProto); } } // no data left or all subform sets matched. if ((nMax != ChildReln.UNLIMITED) && (nCreated == nMax)) { // max number of objects reached stop merging break; } // recompute the list List newList = new ArrayList(); for (Node templateChild = templateNode.getFirstXFAChild(); templateChild != null; templateChild = templateChild.getNextXFASibling()) { if (templateChild instanceof Subform || templateChild instanceof SubformSet) { newList.add((Container)templateChild); } } unusedChildren = newList; // done one round, look for more matches targetProto = findDescendantMatch(unusedChildren, dataParent, connectionDataParent); } if (nCreated == 0) nRequired = getOccurAttribute(templateNode, XFA.INITIALTAG); // remove the number of matched from the required nRequired -= nCreated; // create the remaining required subformsets. for (int nCount = 0; nCount < nRequired; nCount++) { // A CONSUME_DATA merge uses a second pass for data creation, so we just need to create empty // form nodes here. // A MATCH_TEMPLATE merge does everything in a single pass, so we need to create a form node // for the subformSet and then pick a choice child or iterate through all our children. // if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) createEmptyFormNode(templateNode, formParent, connectionDataParent, instanceManager); else { Element subformSet = createFormNode(templateNode, formParent, instanceManager); if (bIsChoice) { // Just grab the first one since there's no data to control choice Container subChild = unusedChildren.get(0); createAndMatchNode(subChild, dataParent, subformSet, connectionDataParent); } else { // Just create them in order since there's no data to control the order createAndMatchChildren(templateNode, dataParent, subformSet, connectionDataParent); } } nCreated++; } return nCreated; } /** * Merges the TemplateModel and the DataModel to create the FormModel. The * first two parameters are implicitly used to call * {@link #setEmptyMerge(boolean)} and {@link #setAdjustData(boolean)} * respectively. * * @param bEmptyMerge * if true then merge against an empty DataModel, * if false use the DataModel found in the * AppModel * @param bAdjustData * if true adjust the structure of the DataModel * to match the structure of the TemplateModel; if * false don't modify the DataModel. * @param bInitialize * if true all the initialize events will be * fired; if false no initialization is performed * @param bRestoreDeltas * if true and if the restoreState property on the * form's root subform is "auto" (the default is "manual"), all * deltas are restored from the form packet. The locale attribute * is always restored from the form packet, regardless of the * value of this parameter. * @param bForceRestore * if true, restore the state regardless of the the * restoreState property on the form's root subform. */ public void merge( boolean bEmptyMerge /* = false */, boolean bAdjustData /* = false */, boolean bInitialize /* = true */, boolean bRestoreDeltas /* = false */, boolean bForceRestore /* = false */) { merge(bEmptyMerge, bAdjustData, "", bInitialize, bRestoreDeltas, bForceRestore); } /** * Merges the TemplateModel and the DataModel to create the FormModel. * * @param bEmptyMerge * if true then merge against an empty DataModel, if * false use the DataModel found in the AppModel * @param bAdjustData * if true, adjust the structure of the DataModel to * match the structure of the TemplateModel; if * false, don't modify the DataModel * @param sConnectionName * if specified get all the data for the merge from the * !connectionData.sConnectionName part of the DataModel. Also, * use connect elements rather than bind elements of the * TemplateModel * @param bInitialize * if true, all the initialize events will be fired; * if false, no initialization is performed * @param bRestoreDeltas * if true and if the restoreState property on the * form's root subform is "auto" (the default is "manual"), all * deltas are restored from the form packet. The locale attribute * is always restored from the form packet, regardless of the * value of this parameter. * @param bForceRestore * if true, restore the state regardless of the the * restoreState property on the form's root subform. * @exclude from published api. */ public void merge(boolean bEmptyMerge /* = false */, boolean bAdjustData /* = false */, String sConnectionName /* = "" */, boolean bInitialize /* = true */, boolean bRestoreDeltas /* = false */, boolean bForceRestore /* = false */) { TraceTimer mergeOnlyTimer = new TraceTimer(TimingType.XFA_MERGE_ONLY_TIMING); try { // Incremental merge is disabled in the plugin, at least for now. There's no // convenient way to disable it since the configuration is in the "present" // section, and since it's really only needed on the server it's easiest just // to disable it in the plugin. if ( ! mbEnableIncrementalMerge) outputTraceMessage(ResId.IncrementalMergeDisabledTrace, null, null, ""); mbWasIncrementalMerge = false; if (mbEnableIncrementalMerge && mbAdjustData == bAdjustData && mbEmptyMerge == bEmptyMerge && incrementalMerge()) { if (bInitialize && !mbExchangingDataWithServer && getRunScripts() != EnumAttr.RUNSCRIPTS_NONE) { if (getXFAParent() != null && getEventManager() != null) getEventManager().reset(); TraceTimer timer = new TraceTimer(TimingType.XFAPA_MERGE_CALC_TIMING); try { // Run initialize scripts initialize(); } finally { timer.stopTiming(); } } notifyPeers(Peer.UPDATED, "", null); outputTraceMessage(ResId.IncrementalMergeSucceededTrace, null, null, ""); mbWasIncrementalMerge = true; return; } if (mbEnableIncrementalMerge) outputTraceMessage(ResId.IncrementalMergeFullMergeTrace, null, null, ""); mActiveField = null; mPrevActiveField = null; // Create an XFA Form DOM from the merge of an XFA Data DOM with an XFA Template DOM // if already done a merge if (mbMergeComplete) reset(); mbAdjustData = bAdjustData; mbEmptyMerge = bEmptyMerge; mbRestoreDeltas = bRestoreDeltas; mbForceRestore = bForceRestore; // setup data and template models preMerge(sConnectionName); // Merge the data into the template // // Roughly speaking, the first pass creates Form DOM nodes as required by the data, // and the second pass creates Data DOM nodes as required by any newly-created Form // DOM nodes. // mergeFirstPass(); mergeSecondPass(this, null); // Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman // Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman // restore values and instance counts. // restore the state only if the root subform is set to restoreState = auto // see http://xtg.can.adobe.com/twiki/bin/view/XFA/FormStateXFAProposal FormSubform deltaSubform = getDeltaSubform(); if (deltaSubform != null) { // turn on is loading, so that we can restore values and instance counts isLoading(true); try { XFAList list = new XFAList(); if (mbRestoreDeltas) mRootFormSubform.getDeltas(deltaSubform, list); else mRootFormSubform.getDeltas(deltaSubform, null); int nLen = list.length(); for (int i = 0; i < nLen; i++) { Delta delta = (Delta)list.item(i); delta.restore(); } } finally { // turn off the loading flag isLoading(false); } } } finally { // Don't include script times or FormServer hacks in this timer. mergeOnlyTimer.stopTiming(); } // if we have a postMergeHandler, call it now if (mPostMergeHandler != null) { mPostMergeHandler.handlePostMerge(mPostMergeHandlerClientData); } // Don't run initialize scripts if we're remerging data that was sent back from // a server. They've already executed once on the client and we don't want them // to reset the user's data! if (bInitialize && !mbExchangingDataWithServer && getRunScripts() != EnumAttr.RUNSCRIPTS_NONE) { TraceTimer timer = new TraceTimer(TimingType.XFAPA_MERGE_CALC_TIMING); try { // Run initialize scripts initialize(); } finally { timer.stopTiming(); } } // FormServer: Look for "$xfa.formState" packet if it exists, and update the form fields updateFromFormState(); // FormServer: Locate overlayData if it exists, and perform the overlay merge mergeOverlayData(); // data merge done - set dynamic properties setDynamicProperties(mRootFormSubform, sConnectionName, true); mbMergeComplete = true; // establish scope for timer if (getRunScripts() != EnumAttr.RUNSCRIPTS_NONE) { TraceTimer timer = new TraceTimer(TimingType.XFAPA_MERGE_CALC_TIMING); try { // FormServer: Locate "$xfa.execEvent" if it exists, and execute any events listed there. runExecEvents(); } finally { timer.stopTiming(); } } // FormServer: Create an updated formState packet if required if (getFormStateUsage()) { createFormState(); } else { // If we don't need to create an updated formState packet, make // sure that any existing formState packet gets removed. Node formState = getAppModel().locateChildByName("formState", 0); if (formState != null) { // watson bug 1573821 // only mark this true if there was content under ; for (Node stateChild = formState.getFirstXMLChild(); stateChild != null; stateChild = stateChild.getNextXMLSibling()) { if (stateChild instanceof Element) { mbFormStateRemoved = true; break; } } formState.remove(); } } // Watson 1069799. Calling for merge() was not automatically informing layout it needed to relayout. // Now notifies peers of formmodel update at the end of merge() call, as opposed to remerge(). notifyPeers(Peer.UPDATED, "", null); } private void mergeOverlayData() { // THIS IS A HACK FOR FORM SERVER!!!!!!!!!!!! // locate and merge the overlay data if (getOverlayDataMergeUsage()) { int nPanel = getPanelToMergeAgainst(); Node overlayData = null; // We will always have at least one data node, so we only // need to look for overlayData if length is greater than 1 for (Node child = mDataModel.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (child.getName() == "overlayData") { overlayData = child; break; } } if (overlayData != null) { // Get the subform to merge with // XFAFormModel oFormModel = getFormModel(); Node topSubform = getFirstXFAChild(); Node targetSubform = null; int nCnt = 0; for (Node subformChild = topSubform.getFirstXFAChild(); subformChild != null; subformChild.getNextXFASibling()) { if (subformChild instanceof FormSubform) { if (nCnt == nPanel) { targetSubform = subformChild; break; } nCnt++; } } if (targetSubform != null) { // do the merge mergeOverlayData(targetSubform, overlayData); // remove the overlayData node from the Data DOM mDataModel.getNodes().remove(overlayData); } else { MsgFormatPos formatError = new MsgFormatPos(ResId.OverlayDataSubformNotFound); throw new ExFull(formatError); } } } } /** * Merges overlayData with a subform (FormServer requirement) * * @param formNode * The subform to merge with * @param overlayData * The data to merge with * * @exclude from published api - method is specific for Form Server. */ public void mergeOverlayData (Node formNode, Node overlayData) { // THIS IS A HACK FOR FORM SERVER!!!!!!!!!!!! // The overlay data will always contain flat xml, but the fields may well // be from nested subforms and will have to be merged into the appropriate // occurrences. // // We also have to look for all non-clicked checkboxes, non-clicked radio // buttons and un-selected selection lists and reset their state to off or // non-selected. // Do the merge of the data by replacing the actual mapped value in the // data DOM with the value specified in the overlayData section. At the // same time, reset state for any unmatched checkboxes, radio buttons and // selection lists as mentioned above for (Node formChild = formNode.getFirstXFAChild(); formChild != null; formChild = formChild.getNextXFASibling()) { // // Adjust instances in form DOM to agree with overlay data // if (formChild instanceof FormSubform) { FormSubform subform = (FormSubform) formChild ; FormInstanceManager manager = subform.getInstanceManager(); int nCount = countOverlayDataChild( formChild, overlayData ); int nInstances = manager.getCount(); int nMin = manager.getMin(); int nMax = manager.getMax(); if (nCount != nInstances) { // // Make sure that we are in the allowable range // if ((nCount >= nMin) && ((nCount <= nMax) || (nMax == -1))) { manager.setInstances(nCount, true); } } } } for (Node formChild = formNode.getFirstXFAChild(); formChild != null; formChild = formChild.getNextXFASibling()) { if (formChild instanceof FormSubform) { Node dataMatch = findUnMappedOverlayDataChild(formChild,overlayData); if (dataMatch != null) { consumeDataNode(null, dataMatch, FormModel.DatasetSelector.MAIN_DATASET); mergeOverlayData (formChild, dataMatch); } else { // // recursive search // mergeOverlayData (formChild, overlayData); } } else if (formChild instanceof FormField) { FormField field = ((FormField)formChild); Node dataMatch = findUnMappedOverlayDataChild(formChild, overlayData); if (dataMatch != null) { // find the child consumeDataNode(null, dataMatch, FormModel.DatasetSelector.MAIN_DATASET); NodeList dataChildren = dataMatch.getNodes(); int nNodes = dataChildren.length(); Node dataChildNode; if (formChild instanceof FormChoiceListField) { // create a dataGroup (in overlayData) and add value nodes to it // String sName = "multiSelect"; // mpoDataModel.createElement(XFA.DATAGROUPTAG, pDataMatch, sName); StringBuilder sValue = new StringBuilder(); for (int i = 0; i < nNodes; i++) { dataChildNode = (Node)dataChildren.item(i); String aName = dataChildNode.getName(); if (aName == XFA.VALUE) { // oDataChildren.remove(oDataChildNode); // (oMSGroupDataNode).appendChild(oDataChildNode); sValue.append(((DataNode)dataChildNode).getValue()); sValue.append('\n'); } } // pField.setDataNode(oMSGroupDataNode,false,false); // (oMSGroupDataNode).setMapped(true); field.setRawValue(sValue.toString()); break; } else { for (int i = 0; i < nNodes; i++) { dataChildNode = (Node)dataChildren.item(i); String aName = dataChildNode.getName(); if (aName == XFA.VALUE) { String sValue = ((DataNode)dataChildNode).getValue(); field.setRawValue(sValue); } else if (aName == STRS.FORMATTEDVALUE) { String sValue = ((DataNode)dataChildNode).getValue(); // // Watson 1196719. // Method setFormattedValue() expects the value to // be a localized formatted value which for numeric // values, in most locales means, formatted with // grouping symbols. Alas values herein may or // may not have grouping symbols. So lets try // parsing them assumming they aren't. // Element valueNode = field.getElement(XFA.VALUETAG, true, 0, true, false); Node contentNode = null; if (valueNode != null) contentNode = valueNode.getOneOfChild(false, true); StringHolder sCanon = new StringHolder(); // // Watson 1199041. // Alas, irrespective of field class, values // herein may or may not have radix symbols, // so lets try parsing them as integers // w/ and w/o grouping symbols first, and // then try parsing them as decimals // w/ and w/o grouping symbols next. // if (!StringUtils.isEmpty(sValue) && contentNode != null && (contentNode.isSameClass(XFA.INTEGERTAG) || contentNode.isSameClass(XFA.FLOATTAG) || contentNode.isSameClass(XFA.DECIMALTAG))) { // Adobe patent application tracking # B136, entitled "Applying locale behaviors to regions of a form", inventors: Gavin McKenzie, Mike Tardif, John Brinkman" String sLocale = field.getInstalledLocale(); LcData data = new LcData(sLocale); String sPict; // try integral w/o groupings symbols. if (StringUtils.isEmpty(sCanon.value)) { sPict = data.getNumberFormat(LcData.INTEGRAL_FMT, LcData.WITHOUT_GROUPINGS); PictureFmt.parseNumeric(sValue, sPict, sLocale, sCanon); } // try integral w/ groupings symbols. if (StringUtils.isEmpty(sCanon.value)) { sPict = data.getNumberFormat(LcData.INTEGRAL_FMT, LcData.WITH_GROUPINGS); PictureFmt.parseNumeric(sValue, sPict, sLocale, sCanon); } // try decimal w/o groupings symbols. if (StringUtils.isEmpty(sCanon.value)) { int nOptn = LcData.WITHOUT_GROUPINGS; LcData data2 = new LcData(sLocale); int nPrec = data2.getNumberPrecision(sValue); nOptn |= LcData.withPrecision(nPrec | 0x80); int nWidth = sValue.length(); if (nWidth > 0) nOptn |= LcData.withWidth(nWidth); sPict = data2.getNumberFormat(LcData.DECIMAL_FMT, nOptn); PictureFmt.parseNumeric(sValue, sPict, sLocale, sCanon); } // try decimal w/ groupings symbols. if (StringUtils.isEmpty(sCanon.value)) { int nOptn = LcData.WITH_GROUPINGS; LcData data2 = new LcData(sLocale); int nPrec = data2.getNumberPrecision(sValue); nOptn |= LcData.withPrecision(nPrec | 0x80); int nWidth = sValue.length(); if (nWidth > 0) nOptn |= LcData.withWidth(nWidth); sPict = data2.getNumberFormat(LcData.DECIMAL_FMT, nOptn); PictureFmt.parseNumeric(sValue, sPict, sLocale, sCanon); } } if (!StringUtils.isEmpty(sCanon.value)) field.setRawValue(sCanon.value); else field.setFormattedValue(sValue); } } } // old way // pField.setDataNode(pDataMatch,false,false); checkForItems(field, dataMatch); } else { // Check if this is a checkbox, radio button or a selection list // if it is, we need to reset the state to // un-checked/un-selected // Get the UI tag Element ui = field.getElement(XFA.UITAG, true, 0, false, false); if (ui != null) { // get current ui Node currentUI = ui.getOneOfChild(true, false); if (currentUI != null) { if (currentUI.isSameClass(XFA.CHECKBUTTONTAG) || currentUI.isSameClass(XFA.CHOICELISTTAG)) { // turn the field off field.setOn(false); } } } } } else if (formChild instanceof FormExclGroup) { FormExclGroup group = ((FormExclGroup)formChild); Node dataMatch = findUnMappedOverlayDataChild(group, overlayData); if ((dataMatch != null)) { // pGroup.setDataNode(pDataMatch,false,false); String sValue = ""; NodeList dataChildren = dataMatch.getNodes(); int nDataNodes = dataChildren.length(); Node dataChildNode; for (int i = 0; i < nDataNodes; i++) { dataChildNode = (Node)dataChildren.item(i); String aName = dataChildNode.getName(); if (aName == XFA.VALUE) { // oValueDataNode = oDataChildNode; // pField.setDataNode(oValueDataNode,false,false); sValue = ((DataNode)dataChildNode).getValue(); } }// endfor // FormServer gives us the name of the radiobutton that is // on as the value in the overlay data. Kill performance here // while figuring out which field is on, getting it's on value, // and setting the value of the exclusion group. NodeList exclGroupChildren = formChild.getNodes(); int nNodes = exclGroupChildren.length(); Node childNode; for (int j = 0; j < nNodes; j++) { childNode = (Node)exclGroupChildren.item(j); if (childNode instanceof FormField) { String sSom = childNode.getSOMExpression(); // get the last part of the som expression, because // that's all form server puts in the overlayData int nFoundAt = sSom.indexOf('.'); boolean bFound = nFoundAt != -1; // search for the field name separator from the // end of the fieldname int saveFoundAt = nFoundAt; if (bFound) { while (true) { nFoundAt = sSom.indexOf(".", saveFoundAt + 1); boolean bFound2 = nFoundAt != -1; if (!bFound2) break; saveFoundAt = nFoundAt; } nFoundAt = saveFoundAt; } String sSub; if (!bFound) { // not a hierarchial name sSub = sSom; } else { sSub = sSom.substring(nFoundAt + 1); } if (sSub.equals(sValue)) { String sOn = ((FormField)childNode).getOnValue(); group.setRawValue(sOn); break; } }// endif }// endfor }// endif } // recursive search for nodes else if (formNode.isContainer()) mergeOverlayData (formChild, overlayData); } } /** * Checks if node is a container element that can merge with data. * * @param node * the node to check if it is mergable * @param bIsConnect * on return, if mergeType returns MATCH_DATAREF, this will be * true if the data ref was on a connect element, false * otherwise. * @return the match type of the node (none, once, scopeless, many, global, * dataRef) * * @exclude from published api. */ int mergeType(Node node, String sConnect /* = "" */, BooleanHolder bIsConnect /* = null */) { // non valid node if (!node.isSameClass(XFA.SUBFORMTAG) && !node.isSameClass(XFA.FIELDTAG) && !node.isSameClass(XFA.EXCLGROUPTAG)) { return EnumAttr.MATCH_NONE; } // check scope = "none" else if (node.isSameClass(XFA.SUBFORMTAG)) { EnumValue eScope = (EnumValue)((Element)node).getAttribute(XFA.SCOPETAG); if (eScope.getInt() == EnumAttr.SCOPE_NONE) return EnumAttr.MATCH_NONE; } // check for ref if we have a data connection boolean bCheckForRef = !StringUtils.isEmpty(sConnect); int eRetValue = EnumAttr.MATCH_ONCE; Container container = (Container)node; Element bind = container.getElement(XFA.BINDTAG, true, 0, false, false); // check to see if we have a bind tag if (bind != null) { EnumValue eType = (EnumValue)bind.getAttribute(XFA.MATCHTAG, true, false); // check match, only dataRef nodes can bind if the have no name if (eType != null) { eRetValue = eType.getInt(); } else { bCheckForRef = true; } } if (bCheckForRef) { String sDataRef = getDataRef(container, msConnectionName, bIsConnect); if (!StringUtils.isEmpty(sDataRef)) eRetValue = EnumAttr.MATCH_DATAREF; } // dataref doesn't need a name, however all other nodes need a name // to merge. if (eRetValue == EnumAttr.MATCH_DATAREF) return eRetValue; else if (node.getName() == "") return EnumAttr.MATCH_NONE; if (eRetValue == EnumAttr.MATCH_ONCE) { if (mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) eRetValue = EnumAttr.MATCH_DESCENDANT; else if (getMatchDescendantsOnly()) eRetValue = EnumAttr.MATCH_DESCENDANT; } return eRetValue; } /** @exclude from published api. */ String metaData(int nOutputType) { return (mHostPseudoModel != null) ? mHostPseudoModel.metaData(nOutputType) : ""; } /** * @see Model#normalizeNameSpaces() * @exclude from published api. */ public void normalizeNameSpaces() { setNameSpaceURI(STRS.XFAFORMNS_CURRENT, false, false, false); for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { if (child instanceof Element) { super.normalizeNameSpaces((Element)child, STRS.XFAFORMNS_CURRENT); } } } /** * Output a trace message. * * @param nResId * the id of the String resource containing the message. * @param inputNode1 * a node to be included in the message; can be null * if not applicable for this message. * @param inputNode2 * a node to be included in the message; can be null * if not applicable for this message. */ private static void outputTraceMessage(int nResId, Node inputNode1 /* = null */, Node inputNode2 /* = null */, String sInput /* = "" */) { if (!Trace.isEnabled("merge", 1)) return; MsgFormatPos msg = new MsgFormatPos(nResId); int nTraceLevel = 0; if (nResId == ResId.StartMergeDataGroup) { // "Merge starts at dataGroup node %1" assert(inputNode1 != null); msg.format("'" + inputNode1.getSOMExpression() + "'"); nTraceLevel = 1; } else if (nResId == ResId.FormNodeMatchedTrace) { // "Successfully matched %1 node %2 with %3 node %4" assert(inputNode1 != null); assert(inputNode2 != null); msg.format(inputNode1.getClassAtom()); msg.format("'" + inputNode1.getSOMExpression() + "'"); msg.format(inputNode2.getClassAtom()); msg.format("'" + inputNode2.getSOMExpression() + "'"); nTraceLevel = 1; } else if (nResId == ResId.IncrementalMergeDisabledTrace) { // "Incremental merge is disabled." nTraceLevel = 1; } else if (nResId == ResId.IncrementalMergeSucceededTrace) { // "Incremental merge succeeded for this record." nTraceLevel = 1; } else if (nResId == ResId.IncrementalMergeFullMergeTrace) { // "Incremental merge did not succeed for this record -- performing // full merge." nTraceLevel = 1; } if (Trace.isEnabled("merge", 2)) { nTraceLevel = 2; if (nResId == ResId.NodeCreatedTrace) { // "Created %1 node %2" assert(inputNode1 != null); msg.format(inputNode1.getClassAtom()); msg.format("'" + inputNode1.getSOMExpression() + "'"); } else if (nResId == ResId.DataNodeMoved) { // "Moved %1 node %2 to %3" assert(inputNode1 != null); assert(!StringUtils.isEmpty(sInput)); msg.format(inputNode1.getClassAtom()); msg.format("'" + sInput + "'"); msg.format("'" + inputNode1.getSOMExpression() + "'"); } else if (nResId == ResId.UnmappedNode) { // "No match found for %1 node %2" assert(inputNode1 != null); msg.format(inputNode1.getClassAtom()); msg.format("'" + inputNode1.getSOMExpression() + "'"); } } if (Trace.isEnabled("merge", 3)) { nTraceLevel = 3; if (nResId == ResId.NodeComparedTrace) { // "Compared %1 with data node %2" assert(inputNode1 != null); assert(inputNode2 != null); msg.format("'" + inputNode1.getSOMExpression() + "'"); msg.format("'" + inputNode2.getSOMExpression() + "'"); } } if (nTraceLevel > 0) Trace.trace("merge", nTraceLevel, msg); } /** @exclude from published api. */ boolean performPreEventValidations() { boolean bValidationSucceeded = true; Validate validate = null; if (getDefaultValidate() != null) validate = getDefaultValidate().clone(); // need to do all the validations if (validate != null) { validate.setFormatTestEnabled(true); validate.setNullTestEnabled(true); validate.setScriptTestEnabled(true); } recalculate(false, validate, true); // perform validations validate(validate, null, true, true); // validations failed if (validate != null && validate.getFailCount() != 0) bValidationSucceeded = false; return bValidationSucceeded; } private boolean postExecEvent(EventManager em, int nEventId, int eReason, Element container) { // Subform/exclGroup 'exit' events also triggers a validate if (container != null && eReason == ScriptHandler.ACTIVITY_EXIT && (container instanceof FormSubform || container instanceof FormExclGroup)) { Validate validate = getDefaultValidate().clone(); if (validate(validate, container, false, false)) // some scripts have been executed return true; } // The validationState event is always fired, for containers that validate, // immediately following the initialize event. else if (container != null && eReason == ScriptHandler.ACTIVITY_INITIALIZE && (container.isSameClass(XFA.SUBFORMTAG) || container.isSameClass(XFA.FIELDTAG) || container.isSameClass(XFA.EXCLGROUPTAG))) { if (fireValidationStateEvent((Container)container)) return true; } // no validation event was dispatched return false; } /** * @see Model#postLoad() * * @exclude from published api. */ protected void postLoad() { } /** * Walk through the form dom and find any unmapped form nodes and attempt * to map them using the data scoping rules. If no match found create new data nodes * with the default values from the template node. This will also move data nodes * to match the structure of the form dom. * * @param formParent * The form start from * @param dataParent * The data node to start from * @exclude from published api. */ void mergeSecondPass(Element formParent, Element dataParent) { // clear the string so that during postMerge, mergeType won't look at the connect elements msConnectionName = ""; mbConnectionMerge = false; //Element dataContext = dataParent != null ? dataParent : mStartNode; // A CONSUME_DATA merge is really a 3-pass merge: the first creates and matches the general // structure of the form DOM, the second (adjustData) creates and/or moves MATCH_ONCE leaf nodes // and the third (findOrCreateMissingData) finds and/or creates MATCH_GLOBAL, MATCH_DESCENDANT // and MATCH_DATAREF leaf nodes. // A MATCH_TEMPLATE merge is single pass, and has already done all the data creation. // if (mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) { if (mbAdjustData && mDataDescription == null) adjustData(formParent, dataParent); findOrCreateMissingData(); } if (mbMergeComplete || dataParent == null){ // Partial merges from importNode() don't do a preMerge(), and therefore don't clear the // initial consumption context. Make sure we leave the deck clear for them. // // Note: this is particularly important for legacy mode as otherwise global field bindings // will get tripped up on the form-data-listener-nuked-my-mapped-flag bug (see CL 568898 // and Watson 2355486). // recursiveDeleteFormInfos(mRootSubform ); mExplicitMatchNodes.clear(); } } private boolean preExecEvent(EventManager em, int nEventId, int eReason, Node container, boolean recursiveCall) { // ensure we have a valid node with a valid model. if (container == null || !container.isContainer() || container.getModel() == null) return false; boolean bEventDispatched = false; // make sure we evaluate the validate, calculate and initialize of all // the fields, exclgroup and subform children of this subfrom // if on merge we also need to send the index changed event to all nodes // or send index changed to the subform nodes. boolean bRecursive = (eReason == ScriptHandler.VALIDATE || eReason == ScriptHandler.CALCULATE || eReason == ScriptHandler.ACTIVITY_INITIALIZE || mbRecursiveIndexChange || (eReason == ScriptHandler.ACTIVITY_INDEXCHANGE && !container.isSameClass(XFA.SUBFORMTAG))); // don't loop through the children if we don't need to. if (bRecursive && container.getClassTag() != XFA.DRAWTAG && container.getClassTag() != XFA.FIELDTAG) { // (watson bug 1576437) clone the node list so that it won't change if one of the nodes gets removed NodeList children = (NodeList)container.getNodes().clone(); for (int i = 0; i < children.length(); i++) { Node child = (Node)children.item(i); if (child != null && child.isContainer() && child.getModel() != null && eventOccurred(em, nEventId, eReason, (Element)child, true)) // some scripts have been executed bEventDispatched = true; } } Element parent = container.getXFAParent(); boolean bNotifyParent = (parent != null && parent.isSameClass(XFA.EXCLGROUPTAG) && !recursiveCall && (eReason == ScriptHandler.ACTIVITY_CHANGE || eReason == ScriptHandler.ACTIVITY_CLICK || eReason == ScriptHandler.ACTIVITY_FULL)); if (bNotifyParent) { bEventDispatched |= eventOccurred(em, nEventId, eReason, parent, false); } if (eReason == ScriptHandler.ACTIVITY_ENTER) { List exitNodes = new ArrayList(); List enterNodes = new ArrayList(); FormField prevField = mPrevActiveField; if (prevField != null) { // The previous active field got its exit event, however // it's ancestral subforms/exclGroups did not. Create a list of // nodes that need exit events. Node prevAncestor = prevField.getXFAParent(); while (prevAncestor != null) { if (prevAncestor instanceof FormSubform || prevAncestor instanceof FormExclGroup) exitNodes.add((Container)prevAncestor); prevAncestor = prevAncestor.getXFAParent(); } } // Before this field receives it's enter event, create // a list of ancestor subforms/exclGroups so that they can // be notified first. if (container.isSameClass(XFA.FIELDTAG)) { if (mActiveField != container) return bEventDispatched; Node ancestor = container.getXFAParent(); while (ancestor != null) { if (ancestor.isSameClass(XFA.SUBFORMTAG) || ancestor.isSameClass(XFA.EXCLGROUPTAG)) enterNodes.add((Container)ancestor); ancestor = ancestor.getXFAParent(); } } // Start comparing list entries starting at the end // and traversing forward. Remove common entries // from both lists. Why? These nodes do not need // an enter or exit event (still considered 'entered'). // Stop once a different node (subform or exclGroup) // is discovered. boolean bDone = false; while ((0 < exitNodes.size() && 0 < enterNodes.size()) && !bDone) { if (exitNodes.get(exitNodes.size() - 1) == enterNodes.get(enterNodes.size() - 1)) { exitNodes.remove(exitNodes.size() - 1); enterNodes.remove(exitNodes.size() - 1); } else bDone = true; } String sExitEvent = EnumAttr.getString(EnumAttr.ACTIVITY_EXIT); int nExitEventId = em.getEventID(sExitEvent); // need to set the mpoPrevActiveField to null otherwise if this field is // in multiple nested subforms they will exit more than once... mPrevActiveField = null; boolean bLegacyV32Scripting = mTemplateModel.getLegacySetting(AppModel.XFA_LEGACY_V32_SCRIPTING); // Send exit events to subforms/exclGroups starting with the lowest // in node hierarchy and working upward for (int i = 0; i < exitNodes.size(); i++) { if (bLegacyV32Scripting){ EventPseudoModel.EventInfo eventInfo = null; if (mEventPseudoModel != null) eventInfo = mEventPseudoModel.getEventInfo(); try { if (mEventPseudoModel != null) { mEventPseudoModel.reset(); mEventPseudoModel.setTarget(exitNodes.get(i)); mEventPseudoModel.setName(ScriptHandler.ACTIVITY_EXIT); } bEventDispatched |= legacyEventOccurred(em, nExitEventId, ScriptHandler.ACTIVITY_EXIT, exitNodes.get(i), false); } finally { if (mEventPseudoModel != null) mEventPseudoModel.setEventInfo(eventInfo); } }else{ bEventDispatched |= eventOccurred(em, nExitEventId, ScriptHandler.ACTIVITY_EXIT, exitNodes.get(i), false); } } // Send enter events to starting with the highest // in the subform/exclGroup hierarchy and working downward // Note that if there were no previous active field the first // subform // to get the enter event would be the root subform. // This doesn't send enter event to the field (that's // done in handleEvent(...)). if (0 < enterNodes.size()) { for (int i = 0; i < enterNodes.size(); i++) { int nIndex = enterNodes.size() - 1 - i; if (bLegacyV32Scripting){ EventPseudoModel.EventInfo eventInfo = null; if (mEventPseudoModel != null) eventInfo = mEventPseudoModel.getEventInfo(); try { if (mEventPseudoModel != null) { mEventPseudoModel.reset(); mEventPseudoModel.setTarget(enterNodes.get(nIndex)); mEventPseudoModel.setName(eReason); } bEventDispatched |= legacyEventOccurred(em, nEventId, eReason, enterNodes.get(nIndex), false); } finally { if (mEventPseudoModel != null) mEventPseudoModel.setEventInfo(eventInfo); } }else{ bEventDispatched |= eventOccurred(em, nEventId, eReason, enterNodes.get(nIndex), false); } } } } return bEventDispatched; } @FindBugsSuppress(code="ES") private void preMerge(String sConnectionName) { boolean bUseEmpty = false; // create a dataModel if one doesn't exist if (mDataModel == null) { AppModel appModel = (AppModel)getXFAParent(); mDataModel = DataModel.getDataModel(appModel, true, false); bUseEmpty = true; } // Grab a start node // Check if we got a record definition for this data model. If we don't, // create a transient record. DataWindow dataWindow = mDataModel.getDataWindow(); if (dataWindow != null && dataWindow.isDefined()) { mStartNode = dataWindow.record(0); // grab the first record only } mRootSubform = null; mRootFormSubform = null; setCurrentVersion(mTemplateModel.getCurrentVersion()); mbIsXFAF = mTemplateModel.getEnum(XFA.BASEPROFILETAG) == EnumAttr.BASEPROFILE_INTERACTIVEFORMS; // get the root subform // Get the top level subforms from the template. for (Node templateNode = mTemplateModel.getFirstXFAChild(); templateNode != null; templateNode = templateNode.getNextXFASibling()) { // Build the Form DOM. // On this level we will have , and // We're only interested in when merging, but // should create the others as well. if (templateNode.isSameClass(XFA.SUBFORMTAG)) { if (mRootSubform == null) mRootSubform = (Subform)templateNode; if (mStartNode != null && templateNode.getName() == mStartNode.getName()) { mRootSubform = (Subform)templateNode; break; } } } if (mRootSubform != null) { // is there a dataDescription for this form? mDataDescription = mDataModel.getDataDescriptionRoot(mRootSubform.getName()); // check the consumption model to be used mbGlobalConsumption = mRootSubform.getEnum(XFA.MERGEMODETAG) == EnumAttr.MERGEMODE_CONSUMEDATA; } boolean bAppendNewNode = false; if (mStartNode == null && mRootSubform != null) { // we will have to do an empty merge bUseEmpty = true; // first try the dataDescription to create a data root if (mDataDescription != null) { mStartNode = mDataModel.createDataRootElement(mDataDescription); // [Jean Young] Append moStartNode to the doc as a work around $record problems in the data description code // [cs] don't think this is still needed but I don't want to break legacy forms if (mStartNode != null) bAppendNewNode = true; } } // need to create a data root if (mRootSubform != null && bUseEmpty) { // then default if (mStartNode == null) { mStartNode = (DataNode)mDataModel.createNode(XFA.DATAGROUPTAG, null, mRootSubform.getName(), "", true); bAppendNewNode = mbAdjustData; } // watson bug 1684858, when we insert a new data make sure it is first. if (bAppendNewNode) { Element data = mDataModel.getAliasNode(); // if (data.getFirstXFAChild() != null) { Node ref = data.getFirstXFAChild(); // grab the first child data.insertChild(mStartNode, ref, true); } else data.appendChild(mStartNode, true); } // set the new root as a our record dataWindow.addRecordGroup(mStartNode); dataWindow.updateAfterLoad(); } if (mRootSubform == null || mStartNode == null) { // no root subform error MsgFormatPos msg = new MsgFormatPos(ResId.RootSubformMergeFailure); throw new ExFull(msg); } // if using a data description only do scopeless matching if (mDataDescription != null) setMatchDescendantsOnly(true); else setMatchDescendantsOnly(false); // make sure we start with a clean context recursiveDeleteFormInfos(mRootSubform); // set empy merge flag mbEmptyMerge = bUseEmpty; // merge with connections only during a non empty merge msConnectionName = sConnectionName; mbConnectionMerge = !StringUtils.isEmpty(msConnectionName); // load the deltas packet into memory loadDeltas(); } // Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman // Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman /** * @see Model#preSave(boolean) * @exclude from published api. */ public void preSave(boolean bSaveXMLScript /* = false */) { // watson bug 1757392, don't dirty the document when saving final boolean previousWillDirty = getWillDirty(); setWillDirty(false); try { if (!bSaveXMLScript) { String sCheckSum = computeCheckSum(); if (!StringUtils.isEmpty(sCheckSum)) setAttribute(new StringAttr(XFA.CHECKSUM, sCheckSum), XFA.CHECKSUMTAG); preSave(mRootFormSubform, null); } normalizeNameSpaces(); } finally { setWillDirty(previousWillDirty); } } // Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman // Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman private void preSave(Node formNode, Element dataParent) { if (formNode == null) return; DataNode dataNode = getDataNode(formNode); // if we have a data node that isn't transparent // and is attached to our document if (dataNode != null && dataNode.getXFAParent() != null && !dataNode.isDefault(false) ) { // don't save the value if we are bound to data if (formNode.isSameClass(XFA.FIELDTAG)) { Element value = ((Element)formNode).getElement(XFA.VALUETAG, 0); // if calc override is set to true we need to save the value node if (value.getEnum(XFA.OVERRIDETAG) == EnumAttr.BOOL_TRUE) { // don't save the value but do save the override attribute value.getOneOfChild().isTransient(true, false); value.isTransient(false, false); value.makeNonDefault(false); } else { // don't save the value node value.isTransient(true, false); } } dataParent = dataNode; } // don't save password field values if (formNode.isSameClass(XFA.FIELDTAG)) { Element fieldElement = ((Element)formNode); Element value = fieldElement.getElement(XFA.VALUETAG, 0); Element ui = fieldElement.getElement(XFA.UITAG, true, 0, false, false); if (ui != null) { Node currentUI = ui.getOneOfChild(true, false); if (currentUI != null && currentUI.isSameClass(XFA.PASSWORDEDITTAG)) value.isTransient(true, false); } } // call preSave on all my children if (formNode.isContainer()) { Element container = (Element)formNode; // store the locale setting away if set to ambient Attribute oLocale = container.getAttribute(XFA.LOCALETAG, true, false); if (oLocale != null) { String sLocale = oLocale.toString(); if (sLocale.equals("ambient")) { // watson bug 1750402, mute the node so layout doesn't get modified since // locale isn't actually changing. formNode.mute(); container.setAttribute(new StringAttr(XFA.LOCALE, getCachedLocale()), XFA.LOCALETAG); formNode.unMute(); } } // if the class and name of the node is not unique then make all instances non transient List nameList = new ArrayList(); for (Node child = formNode.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { preSave(child, dataParent); // add this node to our list, which will get sorted later nameList.add(child); } // only do this work if we are saving the parent if (!formNode.isDefault(false)) { boolean bClearDefault = false; int nFirst = 0; final int nCount = nameList.size(); // if we have another node with the same class and name that is not a default // make then clear the flag for all the nodes Node[] nodes = nameList.toArray(new Node[nCount]); Arrays.sort(nodes, new Comparator() { // Sort by { name, class } public int compare(Node node1, Node node2) { int result = node1.getName().compareTo(node2.getName()); if (result != 0) return result; return node1.getClassTag() < node2.getClassTag() ? -1 : node1.getClassTag() > node2.getClassTag() ? 1 : 0; } }); nameList = null; for (int i = 1; i < nCount; i++) { Node node1 = nodes[i - 1]; Node node2 = nodes[i]; if (node1.getName() != node2.getName() || !node1.isSameClass(node2)) { if (bClearDefault) { for (; nFirst < i; nFirst++) { Node child = nodes[nFirst]; child.makeNonDefault(false); } bClearDefault = false; } nFirst = i; } else { // same name and class bClearDefault |= (!node1.isDefault(false) || !node2.isDefault(false)); } } // handle the last node if (bClearDefault) { for (; nFirst < nCount; nFirst++) { Node child = nodes[nFirst]; child.makeNonDefault(false); } } // if the subform is not a default make sure that the // instanceManager isn't a default if (formNode.isSameClass(XFA.SUBFORMTAG)) { FormInstanceManager manager = ((FormSubform)formNode).getInstanceManager(); if (manager != null) manager.makeNonDefault(false); } // if the subformset is not a default make sure that the // instanceManager isn't a default else if (formNode.isSameClass(XFA.SUBFORMSETTAG)) { FormInstanceManager manager = ((FormSubformSet)formNode).getInstanceManager(); if (manager != null) manager.makeNonDefault(false); } } } else if (formNode.isSameClass(XFA.BORDER) || formNode.isSameClass(XFA.RECTANGLE)) { if (!formNode.isDefault(true)) { // watson bug 1685697 // mark all the children as non default to ensure they are saved out // make then clear the flag for all the nodes for (Node formChild = formNode.getFirstXFAChild(); formNode != null; formNode = formNode.getNextXFASibling()) { if (!formChild.isDefault(true) && (formChild.isSameClass(XFA.EDGETAG) || formChild.isSameClass(XFA.CORNERTAG))) formChild.makeNonDefault(false); } } } } /** * Merge a template model with a data model into a form model. */ private void mergeFirstPass() { // don't move: need to ensure we only create form nodes while doing the first pass of the merge allowNewNodes(true); Element connectDataRoot = null; // grab the connection root // Adobe patent application tracking # P624, entitled Form-based Data Storage And Retrieval, inventors: Matveief,Young,Solc if (mbConnectionMerge) { // this ref will be in the form "Body.etc..." // the actual data will reside in "xdp.datasets.connectionData.connectionName.body.etc..." Node connectData = mDataModel.locateChildByName(XFA.CONNECTIONDATA, 0); if (connectData != null) { String name = msConnectionName.intern(); Node node = connectData.locateChildByName(name, 0); if (node instanceof Element) connectDataRoot = (Element)node; } } if (mbEmptyMerge && mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA) { // The CONSUME_DATA merge algorithm creates empty form nodes in the first pass and // then creates data nodes for them in the second pass. // The MATCH_TEMPLATE merge algorithm does both in a single pass, using the non-emtpy // control flow. // mRootFormSubform = (FormSubform)createEmptyFormNode(mRootSubform, this, connectDataRoot, null); bindNodes(mRootFormSubform, mStartNode, false); consumeDataNode(null, mStartNode, FormModel.DatasetSelector.MAIN_DATASET); } else { mRootFormSubform = (FormSubform)createFormNode(mRootSubform, this, null); bindNodes(mRootFormSubform, mStartNode, false); consumeDataNode(null, mStartNode, FormModel.DatasetSelector.MAIN_DATASET); createAndMatchChildren(mRootSubform, mStartNode, mRootFormSubform, connectDataRoot); } // don't move: need to ensure we only create form nodes while doing the first pass of the merge allowNewNodes(false); } /** * Search nested attributes for a match. (Direct child attributes are handled by getFormInfo * and findMatch.) */ @FindBugsSuppress(code="ES") private DataNode findNestedAttrMatch(Node field, Node dataParent, int eMergeType) { // For scopped matched fields that didn't find a match, try to match against a // nested attribute (those which are children of poDataParent will have already // been picked up by getFormInfo/findMatch). assert(eMergeType == EnumAttr.MATCH_ONCE || eMergeType == EnumAttr.MATCH_DESCENDANT); // no data if (dataParent == null) return null; for (Node dvParent = dataParent.getFirstXFAChild(); dvParent != null; dvParent = dvParent.getNextXFASibling()) { if (dvParent.getClassTag() == XFA.DATAVALUETAG) { for (Node dataChild = dvParent.getFirstXFAChild(); dataChild != null; dataChild = dataChild.getNextXFASibling()) { if (dataChild.getClassTag() != XFA.DATAVALUETAG) continue; DataNode value = (DataNode)dataChild; if (value.isMapped()) continue; if (!value.isAttribute()) continue; if (value.getName() == field.getName()) return value; } } } return null; } /** * Searches for a calculate script, and adds it to the list of pending * calculations if found. * * @param node * the node to search * @return true if a script was found and the node was added * to pending queue (mPendingCalculateNodes); false * otherwise. * * @exclude from published api. */ boolean queueCalculate(Element node) { if (isActivityExcluded("calculate")) return false; List list = mPendingCalculateNodes; if (!canBeQueued(node, true)) return false; // only append if there is script Element action = node.getElement(XFA.CALCULATETAG,true, 0, false, false); Element scriptNode = null; if (action != null) scriptNode = action.getElement(XFA.SCRIPTTAG, true, 0, false, false); if (scriptNode != null) { list.add(node); return true; } return false; } /** * Queues up any nodes that have calculate and/or validate scripts. * * @param node * the node to search for calculate and/or validate scripts * @param bRecursive * if true then the descendents of node are * searched recursively * * @exclude from published api. */ void queueCalculatesAndValidates(Element node, boolean bRecursive /* = true */) { if (node instanceof FormField || node instanceof FormExclGroup) { queueCalculate(node); queueValidate(node); } else if (node.isContainer()) { if (bRecursive) { for (Node child = node.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (!(child instanceof Element)) continue; queueCalculatesAndValidates((Element)child, bRecursive); } } // Queue subform validations after field children validations. if (node instanceof FormSubform) { queueCalculate(node); queueValidate(node); } } if (node instanceof FormModel) { for (int i = 0; i < mLayoutContent.size(); i++) queueCalculatesAndValidates(((LayoutContentInfo)mLayoutContent.get(i)).mNode, bRecursive); } } /** * Searches for a validate script, and adds it to the list of pending * validations if found. * * @param node * the node to search * @return true if a script was found and the node was added * to pending queue (mPendingValidateNodes); false * otherwise. * * @exclude from published api. */ private boolean queueValidate(Element node) { if (isActivityExcluded("validate")) return false; boolean bValidateScriptTest = true; boolean bValidateFormatTest = true; boolean bValidateNullTest = true; boolean bValidateBarcode = false; List list = mPendingValidateNodes; if (!canBeQueued(node, false)) return false; // only append if there is script and/or test(s) Element action = node.getElement(XFA.VALIDATETAG, true, 0, false, false); Node scriptNode = null; Node pictureNode = null; boolean bNullTest = false; if (action != null) { scriptNode = action.getElement(XFA.SCRIPTTAG, true, 0, false, false); int eScriptTest = action.getEnum(XFA.SCRIPTTESTTAG); if (eScriptTest == EnumAttr.TEST_DISABLED || isActivityExcluded("scriptTest") || !bValidateScriptTest) scriptNode = null; int eFormatTest = action.getEnum(XFA.FORMATTESTTAG); if (eFormatTest != EnumAttr.TEST_DISABLED && !isActivityExcluded("formatTest") && bValidateFormatTest) pictureNode = action.getElement(XFA.PICTURETAG, true, 0, false, false); int eNullTest = action.getEnum(XFA.NULLTESTTAG); if (eNullTest != EnumAttr.TEST_DISABLED && !isActivityExcluded("nullTest") && bValidateNullTest) bNullTest = true; } if (node instanceof FormField && ((action == null) || (scriptNode == null && pictureNode == null && !bNullTest))) { Element ui = node.getElement(XFA.UITAG, true, 0, false, false); if (ui != null) { // get current ui Node currentUI = ui.getOneOfChild(true, false); if (currentUI != null) bValidateBarcode = currentUI.isSameClass(XFA.BARCODETAG); } } if (scriptNode != null || pictureNode != null || bNullTest || bValidateBarcode) { list.add(node); return true; } return false; } /** * Executes calculation and validation scripts. After a merge operation, a * full recalculate must be done to ensure that all calculations and * validations are done using current data values. If data values are * changed, this should be called to re-run the calculations.

    * * The FormModel tracks changes to data values, so recalculation efficiency * can be improved by setting the bFullRecalculate parameter to * false to perform only those calculations and validations * that are dependent on the changed data values.

    * * @param bFullRecalculate * if true all calculations and validations are * run. If false calculations and validations are * run as needed (i.e. if values that the validate or calculate * script are dependent on have changed since the last * recalculate). * @param validate * an object derived from Validate. If null the * default Validate is used, and if the default Validate is also null, * no validations are performed. * @param bIgnoreCalcEnabledFlag * if true the calculations will be performed even * if they are disabled on the host. * @return true if any calculations were performed * * @see #setDefaultValidate(Validate) * @see #getDefaultValidate() */ public boolean recalculate( boolean bFullRecalculate /* = false */, Validate validate /* = null */, boolean bIgnoreCalcEnabledFlag /* = false */) { // Watson 1184558, recalculate recursion must be prevented. // Ex: a form control could set the focus on itself // and avoid recursion prevention checks made in // piDocumentContext.recalculate. if (mbIsCalculating) return false; mbIsCalculating = true; mbIgnoreCalcEnabledFlag = bIgnoreCalcEnabledFlag; mValidate = validate; mnValidationRecursionDepth++; try { boolean bRet = false; if (bFullRecalculate) { // clear any queued scripts and validates removeQueuedCalculates(); removeQueuedValidates(); removeQueuedNewValidates(); // Watson #2309916. Improve performance of forms with large numbers of calulations by by-passing the // unnecessary script cyclic and duplicate checks when initially building the list // of scripts to execute. boolean oBoolReset = mbSkipCyclicAndDuplicateCheck; try { mbSkipCyclicAndDuplicateCheck = true; queueCalculatesAndValidates(this, true); // queue up calcs & validates scripts and tests } finally { mbSkipCyclicAndDuplicateCheck = oBoolReset; } } EventManager eventManager = getEventManager(); boolean bFireEvent; boolean bCalcOrValidateFired; preValidate(validate, mnValidationRecursionDepth > 1); do { bCalcOrValidateFired = false; // do all calcs; repeat while the queue is not empty (more entries // may be added by dependency tracking) do { bFireEvent = false; if (getCalculationsEnabled()) { int i = mnNextPendingCalculateNode; while (i < mPendingCalculateNodes.size()) { Node node = mPendingCalculateNodes.get(i); try { if (eventManager.eventOccurred(mnCalcEventId, node)) bRet = true; } catch (ExFull oEx) { // watson 1776934: When an error is thrown during a calculation, catch it, // so that subsequent forms processing can execute MsgFormatPos error = new MsgFormatPos(ResId.UnsupportedOperationException); error.format("calculate").format(node.getSOMExpression()); error.format(". " + oEx.toString()); addErrorList(new ExFull(error), LogMessage.MSG_WARNING, null); } mnNextPendingCalculateNode++; bFireEvent = true; bCalcOrValidateFired = true; i++; } } } while (bFireEvent); // Watson 1145315: Run through any first time validations // and turn off their null test flags. This will ensure that // the validations don't fire before the user has a chance // to type something i.e. if this is coming from an addInstance. if (getValidationsEnabled()) { // Remember previous settings boolean bOldNullTest = false; // Make sure the validate is good. if (mValidate != null) { bOldNullTest = mValidate.isNullTestEnabled(); mValidate.setNullTestEnabled(false); } // Run through the new validates for (Node node: mNewValidateNodes) { if (eventManager.eventOccurred(mnValidateEventId, node)) bRet = true; } // Reset the validate if (mValidate != null) mValidate.setNullTestEnabled(bOldNullTest); // Cleanup the list removeQueuedNewValidates(); } // do all validations; repeat while the queue is not empty (more // entries may be added by dependency tracking) do { bFireEvent = false; if (getValidationsEnabled()) { int i = mnNextPendingValidateNode; while (i < mPendingValidateNodes.size()) { Node node = mPendingValidateNodes.get(i); if (eventManager.eventOccurred(mnValidateEventId, node)) bRet = true; mnNextPendingValidateNode++; bFireEvent = true; bCalcOrValidateFired = true; i++; } } } while (bFireEvent); } while (bCalcOrValidateFired); postValidate(validate, mnValidationRecursionDepth > 1); if (getCalculationsEnabled()) removeQueuedCalculates(); if (getValidationsEnabled()) removeQueuedValidates(); mnNextPendingCalculateNode = 0; mnNextPendingValidateNode = 0; // issue an "$form:ready" event every time a full recalculate occurs if (ready(bFullRecalculate)) bRet = true; // for form server callouts if (eventManager.eventOccurred(eventManager.getEventID("overlay"), this)) bRet = true; return bRet; } finally { mbIgnoreCalcEnabledFlag = false; mbIsCalculating = false; mValidate = null; mnValidationRecursionDepth--; } } private boolean getRegistered(Node node, int eActivity) { if (node instanceof FormField) { return ((FormField) node).getRegistered(eActivity); } else if (node instanceof FormSubform) { return ((FormSubform) node).getRegistered(eActivity); } else if (node instanceof FormExclGroup) { return ((FormExclGroup) node).getRegistered(eActivity); } return false; } private void setRegistered(Node node, int eActivity) { if (node instanceof FormField) { FormField oField = ((FormField) node); oField.setRegistered(eActivity); } else if (node instanceof FormSubform) { FormSubform oSubform = ((FormSubform) node); oSubform.setRegistered(eActivity); } else if (node instanceof FormExclGroup) { FormExclGroup oExclGroup = ((FormExclGroup) node); oExclGroup.setRegistered(eActivity); } } /** @exclude from published api. */ void registerEvents(Element element, int nEventTypes /* = XFAEVENTTYPE_ALL */) { assert element != null; if (!mbRegisterNewEvents) return; EventManager eventManager = getEventManager(); if (eventManager == null) return; boolean bEvents = (nEventTypes & FormModel.XFAEVENTTYPE_EVENTS) != 0; boolean bCalculate = ((nEventTypes & XFAEVENTTYPE_CALCULATE) != 0) && !getRegistered(element, XFA.CALCULATETAG); boolean bValidate = ((nEventTypes & XFAEVENTTYPE_VALIDATE) != 0) && !getRegistered(element, XFA.VALIDATETAG); if (element.isSameClass(XFA.FIELDTAG) || element.isSameClass(XFA.SUBFORMTAG) || element.isSameClass(XFA.EXCLGROUPTAG)) { ProtoableNode container = (ProtoableNode)element; // register calculate script if (bCalculate && !isActivityExcluded("calculate")) { FormModel.ScriptInfo calculateInfo = getCalculateInfo(container); if (calculateInfo != null) { CalculateDispatcher cd = new CalculateDispatcher(calculateInfo.mScriptContextNode, calculateInfo.msEventContext, mnCalcEventId, eventManager, calculateInfo.msScript, calculateInfo.msScriptLanguage, calculateInfo.meRunAt); eventManager.registerEvents(cd); // TODO: There's no bug here, but could be a performance gain. // If we see duplicate events firing, we can add a new moNewCalculate // list, like we are doing below for the new validation lists. // (CS, GL, SS) mPendingCalculateNodes.add(container); setRegistered(element, XFA.CALCULATETAG); } } // register validate script if (bValidate && !isActivityExcluded("validate")) { ValidateInfo validateInfo = getValidateInfo(container); if (validateInfo != null) { ValidateDispatcher vd = new ValidateDispatcher(validateInfo.mScriptContextNode, validateInfo.msEventContext, mnValidateEventId, eventManager, validateInfo.msScript, validateInfo.msScriptLanguage, validateInfo.msBarcodeType, validateInfo.meRunAt); eventManager.registerEvents(vd); // Watson 1145315: This is where all validates first get added. // Now, instead of adding them directly to the pending list, we // will add them to a new initialization list. We do this so // that we can make sure a nullTest doesn't fire before the // user has a chance to type something into it. mNewValidateNodes.add(container); setRegistered(element, XFA.VALIDATETAG); } } if (element.isSameClass(XFA.FIELDTAG)) { // register all the other events for (Node child = element.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (child.isSameClass(XFA.EVENTTAG)) registerEvents((Element)child, nEventTypes); } } } else if (bEvents && element.isSameClass(XFA.EVENTTAG)) { // get the