joerg 2004/03/11 19:31:40
Modified: src/blocks/forms/samples/forms form2_bind_bean.xml form2_bind_xml.xml src/blocks/forms/java/org/apache/cocoon/forms/binding RepeaterJXPathBindingBuilder.java ValueJXPathBinding.java RepeaterJXPathBinding.java . status.xml Removed: src/blocks/forms/java/org/apache/cocoon/forms/binding UniqueFieldJXPathBinding.java UniqueFieldJXPathBindingBuilder.java Log: issue 17600: syntax update for repeater row identity handling Revision Changes Path 1.2 +6 -3 cocoon-2.1/src/blocks/forms/samples/forms/form2_bind_bean.xml Index: form2_bind_bean.xml =================================================================== RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/samples/forms/form2_bind_bean.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- form2_bind_bean.xml 9 Mar 2004 10:33:48 -0000 1.1 +++ form2_bind_bean.xml 12 Mar 2004 03:31:39 -0000 1.2 @@ -48,16 +48,19 @@ <!-- repeater requires unique identification mechanism of the row-nodes --> <!-- (it is of course possible to implement other binding strategies) --> + <!-- important note: the row-path is used inside jxpath-createPath context, + as a consequence it cannot have dependent children or predicates --> <fb:repeater id="contacts" parent-path="." row-path="contacts" unique-row-id="id" unique-path="@id"> - <!-- compare to xml-binding: the convertor is not needed here --> + <fb:identity> + <!-- compare to xml-binding: the convertor is not needed here --> + <fb:value id="id" path="@id"/> + </fb:identity> - <!-- important note: the row-path is used inside jxpath-createPath context, - as a consequence it cannot have dependent children or predicates --> <fb:on-bind> <!-- executed on updates AND right after the insert --> 1.2 +11 -10 cocoon-2.1/src/blocks/forms/samples/forms/form2_bind_xml.xml Index: form2_bind_xml.xml =================================================================== RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/samples/forms/form2_bind_xml.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- form2_bind_xml.xml 9 Mar 2004 10:33:48 -0000 1.1 +++ form2_bind_xml.xml 12 Mar 2004 03:31:39 -0000 1.2 @@ -69,18 +69,19 @@ <fb:multi-value id="drinks" parent-path="drinks" row-path="drink"/> <!-- repeater requires unique identification mechanism of the row-nodes --> + <!-- important note: the row-path is used inside jxpath-createPath context, + as a consequence it cannot have dependent children or predicates --> <fb:repeater id="contacts" parent-path="contacts" - row-path="contact" - unique-row-id="id" - unique-path="@id" - > - - <!-- optional convertor to use for mapping the unique id --> - <fd:convertor datatype="long" /> - - <!-- important note: the row-path is used inside jxpath-createPath context, - as a consequence it cannot have dependent children or predicates --> + row-path="contact"> + + <fb:identity> + <fb:value id="id" path="@id"> + <!-- optional convertor to use for mapping the unique id --> + <fd:convertor datatype="long" /> + </fb:value> + </fb:identity> + <fb:on-bind> <!-- executed on updates AND right after the insert --> 1.3 +12 -48 cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/binding/RepeaterJXPathBindingBuilder.java Index: RepeaterJXPathBindingBuilder.java =================================================================== RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/binding/RepeaterJXPathBindingBuilder.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- RepeaterJXPathBindingBuilder.java 9 Mar 2004 13:08:46 -0000 1.2 +++ RepeaterJXPathBindingBuilder.java 12 Mar 2004 03:31:39 -0000 1.3 @@ -15,12 +15,7 @@ */ package org.apache.cocoon.forms.binding; -import java.util.Locale; - -import org.apache.cocoon.forms.Constants; -import org.apache.cocoon.forms.datatype.convertor.Convertor; import org.apache.cocoon.forms.util.DomHelper; -import org.apache.cocoon.i18n.I18nUtils; import org.w3c.dom.Element; /** @@ -76,30 +71,9 @@ String rowPath = DomHelper.getAttribute(bindingElm, "row-path"); String rowPathForInsert = DomHelper.getAttribute(bindingElm, "row-path-insert", rowPath); - String uniqueRowId = - DomHelper.getAttribute(bindingElm, "unique-row-id", null); - String uniqueRowIdPath = - DomHelper.getAttribute(bindingElm, "unique-path", null); - - Convertor convertor = null; - Locale convertorLocale = Locale.US; - Element convertorEl = - DomHelper.getChildElement(bindingElm, - Constants.DEFINITION_NS, "convertor"); - if (convertorEl != null) { - String datatype = - DomHelper.getAttribute(convertorEl, "datatype"); - String localeStr = convertorEl.getAttribute("datatype"); - if (!localeStr.equals("")) { - convertorLocale = I18nUtils.parseLocale(localeStr); - } - convertor = - assistant.getDatatypeManager().createConvertor(datatype, - convertorEl); - } - Element childWrapElement = DomHelper.getChildElement(bindingElm, - BindingManager.NAMESPACE, "on-bind"); + Element childWrapElement = + DomHelper.getChildElement(bindingElm, BindingManager.NAMESPACE, "on-bind"); if (childWrapElement == null) { throw new BindingException( "RepeaterBinding misses '<on-bind>' child definition. " + @@ -123,29 +97,19 @@ insertBinding = assistant.makeChildBindings(insertWrapElement)[0]; } - /* New <wb:unique-row> child element builder */ - Element uniqueFieldWrapElement = DomHelper.getChildElement(bindingElm, - BindingManager.NAMESPACE, "unique-row"); - JXPathBindingBase[] uniqueFieldBinding = null; - if (uniqueFieldWrapElement != null) { - uniqueFieldBinding = assistant.makeChildBindings(uniqueFieldWrapElement); - } else if (uniqueRowId == null || uniqueRowIdPath == null) { - throw new BindingException( - "RepeaterBinding misses '<unique-row>' child definition. " + - DomHelper.getLocation(bindingElm)); - } else { - if (this.getLogger().isInfoEnabled()) { - this.getLogger().info("<wb:repeater>: The attributes 'unique-row-id' and " + - "'unique-path' are deprecated. Use <unique-row> child element instead." + - " Located at " + DomHelper.getLocation(bindingElm)); - } + + Element identityWrapElement = DomHelper.getChildElement(bindingElm, + BindingManager.NAMESPACE, "identity"); + JXPathBindingBase[] identityBinding = null; + if (identityWrapElement != null) { + identityBinding = + assistant.makeChildBindings(identityWrapElement); } RepeaterJXPathBinding repeaterBinding = new RepeaterJXPathBinding(commonAtts, repeaterId, parentPath, - rowPath, rowPathForInsert, uniqueRowId, - uniqueRowIdPath, convertor, convertorLocale, - childBindings, insertBinding, deleteBindings, uniqueFieldBinding); + rowPath, rowPathForInsert, + childBindings, insertBinding, deleteBindings, identityBinding); return repeaterBinding; } catch (BindingException e) { throw e; 1.3 +17 -1 cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/binding/ValueJXPathBinding.java Index: ValueJXPathBinding.java =================================================================== RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/binding/ValueJXPathBinding.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- ValueJXPathBinding.java 11 Mar 2004 02:56:32 -0000 1.2 +++ ValueJXPathBinding.java 12 Mar 2004 03:31:39 -0000 1.3 @@ -154,4 +154,20 @@ super.enableLogging(logger); this.updateBinding.enableLogging(logger); } + + public String getFieldId() { + return this.fieldId; + } + + public String getXPath() { + return this.xpath; + } + + public Convertor getConvertor() { + return this.convertor; + } + + public Locale getConvertorLocale() { + return this.convertorLocale; + } } 1.2 +85 -99 cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/binding/RepeaterJXPathBinding.java Index: RepeaterJXPathBinding.java =================================================================== RCS file: /home/cvs/cocoon-2.1/src/blocks/forms/java/org/apache/cocoon/forms/binding/RepeaterJXPathBinding.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- RepeaterJXPathBinding.java 9 Mar 2004 10:33:55 -0000 1.1 +++ RepeaterJXPathBinding.java 12 Mar 2004 03:31:39 -0000 1.2 @@ -19,11 +19,9 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Set; import org.apache.avalon.framework.logger.Logger; -import org.apache.cocoon.forms.datatype.convertor.Convertor; import org.apache.cocoon.forms.formmodel.Repeater; import org.apache.cocoon.forms.formmodel.Widget; import org.apache.commons.collections.ListUtils; @@ -46,7 +44,7 @@ private final JXPathBindingBase rowBinding; private final JXPathBindingBase insertRowBinding; private final JXPathBindingBase deleteRowBinding; - private final List uniqueRowBinding; + private final ComposedJXPathBindingBase identityBinding; /** * Constructs RepeaterJXPathBinding @@ -54,56 +52,37 @@ public RepeaterJXPathBinding( JXPathBindingBuilderBase.CommonAttributes commonAtts, String repeaterId, String repeaterPath, String rowPath, - String rowPathForInsert, String uniqueRowId, - String uniqueRowPath, JXPathBindingBase[] childBindings, - JXPathBindingBase insertBinding, - JXPathBindingBase[] deleteBindings, JXPathBindingBase[] uniqueBindings) { - this(commonAtts, repeaterId, repeaterPath, rowPath, rowPathForInsert, - uniqueRowId, uniqueRowPath, null, null, childBindings, - insertBinding, deleteBindings, uniqueBindings); - } - - /** - * Constructs RepeaterJXPathBinding - */ - public RepeaterJXPathBinding( - JXPathBindingBuilderBase.CommonAttributes commonAtts, - String repeaterId, String repeaterPath, String rowPath, - String rowPathForInsert, String uniqueRowId, - String uniqueRowPath, Convertor convertor, Locale convertorLocale, + String rowPathForInsert, JXPathBindingBase[] childBindings, JXPathBindingBase insertBinding, - JXPathBindingBase[] deleteBindings, JXPathBindingBase[] uniqueBindings) { + JXPathBindingBase[] deleteBindings, JXPathBindingBase[] identityBindings) { super(commonAtts); this.repeaterId = repeaterId; this.repeaterPath = repeaterPath; this.rowPath = rowPath; this.rowPathForInsert = rowPathForInsert; + this.rowBinding = new ComposedJXPathBindingBase( - JXPathBindingBuilderBase.CommonAttributes.DEFAULT, - childBindings); + JXPathBindingBuilderBase.CommonAttributes.DEFAULT, + childBindings); this.rowBinding.setParent(this); + this.insertRowBinding = insertBinding; if (this.insertRowBinding != null) { this.insertRowBinding.setParent(this); } + this.deleteRowBinding = new ComposedJXPathBindingBase( - JXPathBindingBuilderBase.CommonAttributes.DEFAULT, - deleteBindings); + JXPathBindingBuilderBase.CommonAttributes.DEFAULT, + deleteBindings); if (this.deleteRowBinding != null) { this.deleteRowBinding.setParent(this); } - // New unique key management - uniqueRowBinding = new ArrayList(); - // Create a UniqueFieldJXPathBining for the unique define in old-style - if (uniqueRowId != null && uniqueRowPath != null) { - uniqueRowBinding.add(new UniqueFieldJXPathBinding( - JXPathBindingBuilderBase.CommonAttributes.DEFAULT, - uniqueRowId, uniqueRowPath, convertor, convertorLocale)); - } - if (uniqueBindings != null) { - for (int i=0; i < uniqueBindings.length; i++) { - uniqueRowBinding.add(uniqueBindings[i]); - } + + this.identityBinding = new ComposedJXPathBindingBase( + JXPathBindingBuilderBase.CommonAttributes.DEFAULT, + identityBindings); + if (this.identityBinding != null) { + this.identityBinding.setParent(this); } } @@ -136,10 +115,7 @@ Pointer jxp = (Pointer)rowPointers.next(); JXPathContext rowContext = repeaterContext.getRelativeContext(jxp); // hand it over to children - Iterator iter = this.uniqueRowBinding.iterator(); - while (iter.hasNext()) { - ((UniqueFieldJXPathBinding)iter.next()).loadFormFromModel(thisRow, rowContext); - } + this.identityBinding.loadFormFromModel(thisRow, rowContext); this.rowBinding.loadFormFromModel(thisRow, rowContext); } if (getLogger().isDebugEnabled()) @@ -147,9 +123,9 @@ } /** - * Uses the mapped unique-id of each row to detect if rows have been + * Uses the mapped identity of each row to detect if rows have been * updated, inserted or removed. Depending on what happened the appropriate - * child-bindings are alowed to visit the narrowed contexts. + * child-bindings are allowed to visit the narrowed contexts. */ public void doSave(Widget frmModel, JXPathContext jxpc) throws BindingException { @@ -160,7 +136,7 @@ jxpc.getRelativeContext(jxpc.getPointer(this.repeaterPath)); // create set of updatedRowIds - Set updatedRowIds = new HashSet(); + Set updatedRows = new HashSet(); //create list of rows to insert at end List rowsToInsert = new ArrayList(); @@ -169,22 +145,22 @@ for (int i = 0; i < formRowCount; i++) { Repeater.RepeaterRow thisRow = repeater.getRow(i); - // Get the key values - List rowIdValues = getUniqueRowValues(thisRow); + // Get the identity + List identity = getIdentity(thisRow); - if (isAnyListElementNotNull(rowIdValues)) { + if (hasNonNullElements(identity)) { // iterate nodes to find match Iterator rowPointers = repeaterContext.iteratePointers(this.rowPath); boolean found = false; while (rowPointers.hasNext()) { Pointer jxp = (Pointer) rowPointers.next(); JXPathContext rowContext = repeaterContext.getRelativeContext(jxp); - List matchIds = getMatchIds(rowContext); - if (ListUtils.isEqualList(rowIdValues, matchIds)) { + List contextIdentity = getIdentity(rowContext); + if (ListUtils.isEqualList(identity, contextIdentity)) { // match! --> bind to children this.rowBinding.saveFormToModel(thisRow, rowContext); // --> store rowIdValue in list of updatedRowIds - updatedRowIds.add(rowIdValues); + updatedRows.add(identity); found = true; break; } @@ -193,10 +169,10 @@ // this is a new row rowsToInsert.add(thisRow); // also add it to the updated row id's so that this row doesn't get deleted - updatedRowIds.add(rowIdValues); + updatedRows.add(identity); } } else { - // if all rowIdValues == null --> this is a new row + // if there is no value to determine the identity --> this is a new row rowsToInsert.add(thisRow); } } @@ -206,9 +182,10 @@ while (rowPointers.hasNext()) { Pointer jxp = (Pointer)rowPointers.next(); JXPathContext rowContext = repeaterContext.getRelativeContext((Pointer)jxp.clone()); - List matchIds = getMatchIds(rowContext); - // check if matchPath was in list of updates, if not --> bind for delete - if (!isListInSet(updatedRowIds, matchIds)) { + List contextIdentity = getIdentity(rowContext); + // check if the identity of the rowContext is in the updated rows + // if not --> bind for delete + if (!isIdentityInUpdatedRows(updatedRows, contextIdentity)) { rowsToDelete.add(rowContext); } } @@ -260,8 +237,8 @@ } } else { if (getLogger().isWarnEnabled()) { - getLogger().warn("RepeaterBinding has detected rows to insert, but misses " + - "the <on-insert-row> binding to do it."); + getLogger().warn("RepeaterBinding has detected rows to insert, but misses " + + "the <on-insert-row> binding to do it."); } } } @@ -271,16 +248,16 @@ } /** - * Tests if a List is already contained in a Set of Lists. - * @param set the Set of Lists. - * @param list the list that is tested if it is already in the Set. - * @return true if the Set contains the List, false otherwise. + * Tests if an identity is already contained in a Set of identities. + * @param identitySet the Set of identities. + * @param identity the identity that is tested if it is already in the Set. + * @return true if the Set contains the identity, false otherwise. */ - private boolean isListInSet(Set set, List list) { - Iterator iter = set.iterator(); + private boolean isIdentityInUpdatedRows(Set identitySet, List identity) { + Iterator iter = identitySet.iterator(); while (iter.hasNext()) { - List listFromSet = (List)iter.next(); - if (ListUtils.isEqualList(listFromSet, list)) { + List identityFromSet = (List)iter.next(); + if (ListUtils.isEqualList(identityFromSet, identity)) { return true; } } @@ -292,7 +269,7 @@ * @param list * @return */ - private boolean isAnyListElementNotNull(List list) { + private boolean hasNonNullElements(List list) { Iterator iter = list.iterator(); while (iter.hasNext()) { if (iter.next() != null) { @@ -303,47 +280,57 @@ } /** - * + * Get the identity of the given row context. That's infact a list of all + * the values of the fields in the bean or XML that constitute the identity. * @param rowContext - * @return + * @return List the identity of the row context */ - private List getMatchIds(JXPathContext rowContext) { - List matchIds = new ArrayList(); - Iterator iter = this.uniqueRowBinding.iterator(); - while (iter.hasNext()) { - UniqueFieldJXPathBinding key = (UniqueFieldJXPathBinding)iter.next(); - Object matchId = rowContext.getValue(key.getXpath()); - if (matchId != null && key.getConvertor() != null) { - if (matchId instanceof String) { - matchId = key.getConvertor().convertFromString( - (String)matchId, key.getConvertorLocale(), null); - } else { - if (getLogger().isWarnEnabled()) { - getLogger().warn("Convertor ignored on backend-value " + - "which isn't of type String."); + private List getIdentity(JXPathContext rowContext) { + List identity = new ArrayList(); + + JXPathBindingBase[] childBindings = this.identityBinding.getChildBindings(); + if (childBindings != null) { + int size = childBindings.length; + for (int i = 0; i < size; i++) { + ValueJXPathBinding vBinding = (ValueJXPathBinding)childBindings[i]; + Object value = rowContext.getValue(vBinding.getXPath()); + if (value != null && vBinding.getConvertor() != null) { + if (value instanceof String) { + value = vBinding.getConvertor().convertFromString( + (String)value, vBinding.getConvertorLocale(), null); + } else { + if (getLogger().isWarnEnabled()) { + getLogger().warn("Convertor ignored on backend-value " + + "which isn't of type String."); + } } } + identity.add(value); } - matchIds.add(matchId); } - return matchIds; + return identity; } /** - * Get the values of the unique-fields of the given row in the formModel + * Get the identity of the given row. That's infact a list of all the values + * of the fields in the form model that constitute the identity. * @param thisRow - * @return List + * @return List the identity of the row */ - private List getUniqueRowValues(Repeater.RepeaterRow thisRow) { - List values = new ArrayList(); - Iterator iter = this.uniqueRowBinding.iterator(); - while (iter.hasNext()) { - UniqueFieldJXPathBinding key = (UniqueFieldJXPathBinding)iter.next(); - Widget rowIdWidget = thisRow.getWidget(key.getFieldId()); - Object rowIdValue = rowIdWidget.getValue(); - values.add(rowIdValue); + private List getIdentity(Repeater.RepeaterRow row) { + List identity = new ArrayList(); + + JXPathBindingBase[] childBindings = this.identityBinding.getChildBindings(); + if (childBindings != null) { + int size = childBindings.length; + for (int i = 0; i < size; i++) { + String fieldId = ((ValueJXPathBinding)childBindings[i]).getFieldId(); + Widget widget = row.getWidget(fieldId); + Object value = widget.getValue(); + identity.add(value); + } } - return values; + return identity; } public String toString() { @@ -360,9 +347,8 @@ this.insertRowBinding.enableLogging(logger); } this.rowBinding.enableLogging(logger); - Iterator iter = this.uniqueRowBinding.iterator(); - while (iter.hasNext()) { - ((UniqueFieldJXPathBinding)iter.next()).enableLogging(logger); + if (this.identityBinding != null) { + this.identityBinding.enableLogging(logger); } } } 1.275 +15 -9 cocoon-2.1/status.xml Index: status.xml =================================================================== RCS file: /home/cvs/cocoon-2.1/status.xml,v retrieving revision 1.274 retrieving revision 1.275 diff -u -r1.274 -r1.275 --- status.xml 11 Mar 2004 14:21:56 -0000 1.274 +++ status.xml 12 Mar 2004 03:31:40 -0000 1.275 @@ -28,7 +28,13 @@ <!ATTLIST actions priority (high | medium | low) #REQUIRED > -<!ELEMENT action (#PCDATA | link | br | code)*> +<!ELEMENT changes (release+)> +<!ELEMENT release (action+)> +<!ATTLIST release + version CDATA #REQUIRED + date CDATA #REQUIRED +> +<!ELEMENT action (#PCDATA | link | br | code | ul)*> <!ATTLIST action context (build | code | docs) #IMPLIED assigned-to CDATA #IMPLIED @@ -38,18 +44,14 @@ due-to CDATA #IMPLIED due-to-email CDATA #IMPLIED > -<!ELEMENT changes (release+)> <!ELEMENT code (#PCDATA)> <!ELEMENT br EMPTY> <!ELEMENT link (#PCDATA)> <!ATTLIST link href CDATA #REQUIRED > -<!ELEMENT release (action+)> -<!ATTLIST release - version CDATA #REQUIRED - date CDATA #REQUIRED -> +<!ELEMENT ul (li)+> +<!ELEMENT li (#PCDATA | link | br | code | ul)*> <!ENTITY eacute "é"> <!ENTITY ouml "ö"> <!ENTITY uuml "ü"> @@ -210,6 +212,9 @@ <changes> <release version="@version@" date="@date@"> + <action dev="JH" type="update" fixes-bug="27600"> + CForms: Change the syntax for repeater row identity handling. + </action> <action dev="CZ" type="update"> An internal redirect is now handled the same way as an external redirect would be handled. This includes that the error handler of the called pipeline is invoked in case of an error. @@ -253,7 +258,8 @@ to xmlns:fi="http://apache.org/cocoon/forms/1.0#instance"</li> <li>from xmlns:wt="http://apache.org/cocoon/woody/template/1.0" to xmlns:ft="http://apache.org/cocoon/forms/1.0#template"</li> - </ul></li> + </ul> + </li> <li>Java packages moved from org.apache.cocoon.woody to org.apache.cocoon.forms</li> </ul> </action>