KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > forms > binding > RepeaterJXPathBinding


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.forms.binding;
17
18 import java.util.ArrayList JavaDoc;
19 import java.util.Collections JavaDoc;
20 import java.util.HashSet JavaDoc;
21 import java.util.Iterator JavaDoc;
22 import java.util.List JavaDoc;
23 import java.util.Set JavaDoc;
24
25 import org.apache.avalon.framework.logger.Logger;
26 import org.apache.cocoon.forms.formmodel.Repeater;
27 import org.apache.cocoon.forms.formmodel.Widget;
28 import org.apache.cocoon.forms.datatype.convertor.ConversionResult;
29 import org.apache.commons.collections.ListUtils;
30 import org.apache.commons.jxpath.JXPathContext;
31 import org.apache.commons.jxpath.Pointer;
32
33 /**
34  * RepeaterJXPathBinding provides an implementation of a {@link Binding}
35  * that allows for bidirectional binding of a repeater-widget to/from
36  * repeating structures in the back-end object model.
37  *
38  * @version $Id: RepeaterJXPathBinding.java 290473 2005-09-20 15:30:48Z sylvain $
39  */

40 public class RepeaterJXPathBinding extends JXPathBindingBase {
41     
42     private final String JavaDoc repeaterId;
43     private final String JavaDoc repeaterPath;
44     private final String JavaDoc rowPath;
45     private final String JavaDoc rowPathForInsert;
46     private final JXPathBindingBase rowBinding;
47     private final JXPathBindingBase insertRowBinding;
48     private final JXPathBindingBase deleteRowBinding;
49     private final ComposedJXPathBindingBase identityBinding;
50     
51     /**
52      * Constructs RepeaterJXPathBinding
53      */

54     public RepeaterJXPathBinding(
55             JXPathBindingBuilderBase.CommonAttributes commonAtts,
56             String JavaDoc repeaterId, String JavaDoc repeaterPath, String JavaDoc rowPath,
57             String JavaDoc rowPathForInsert,
58             JXPathBindingBase[] childBindings, JXPathBindingBase insertBinding,
59             JXPathBindingBase[] deleteBindings, JXPathBindingBase[] identityBindings) {
60         super(commonAtts);
61         this.repeaterId = repeaterId;
62         this.repeaterPath = repeaterPath;
63         this.rowPath = rowPath;
64         this.rowPathForInsert = rowPathForInsert;
65         
66         this.rowBinding = new ComposedJXPathBindingBase(
67                 JXPathBindingBuilderBase.CommonAttributes.DEFAULT,
68                 childBindings);
69         this.rowBinding.setParent(this);
70         
71         this.insertRowBinding = insertBinding;
72         if (this.insertRowBinding != null) {
73             this.insertRowBinding.setParent(this);
74         }
75         
76         if (deleteBindings != null) {
77             this.deleteRowBinding = new ComposedJXPathBindingBase(
78                     JXPathBindingBuilderBase.CommonAttributes.DEFAULT,
79                     deleteBindings);
80             this.deleteRowBinding.setParent(this);
81         } else {
82             this.deleteRowBinding = null;
83         }
84         
85         
86         if (identityBindings != null) {
87             
88             this.identityBinding = new ComposedJXPathBindingBase(
89                     JXPathBindingBuilderBase.CommonAttributes.DEFAULT,
90                     identityBindings);
91             this.identityBinding.setParent(this);
92         }
93         else
94             this.identityBinding = null;
95     }
96     
97     public String JavaDoc getId() { return repeaterId; }
98     public String JavaDoc getRepeaterPath() { return repeaterPath; }
99     public String JavaDoc getRowPath() { return rowPath; }
100     public String JavaDoc getInsertRowPath() { return rowPathForInsert; }
101     public ComposedJXPathBindingBase getRowBinding() { return (ComposedJXPathBindingBase)rowBinding; }
102     public ComposedJXPathBindingBase getDeleteRowBinding() { return (ComposedJXPathBindingBase)deleteRowBinding; }
103     public ComposedJXPathBindingBase getIdentityBinding() { return (ComposedJXPathBindingBase)identityBinding; }
104     public JXPathBindingBase getInsertRowBinding() { return insertRowBinding; }
105     
106     /**
107      * Binds the unique-id of the repeated rows, and narrows the context on
108      * objectModelContext and Repeater to the repeated rows before handing
109      * over to the actual binding-children.
110      */

111     public void doLoad(Widget frmModel, JXPathContext jxpc)
112     throws BindingException {
113         // Find the repeater
114
Repeater repeater = (Repeater) selectWidget(frmModel, this.repeaterId);
115         if (repeater == null) {
116             throw new BindingException("The repeater with the ID [" + this.repeaterId
117                     + "] referenced in the binding does not exist in the form definition.");
118         }
119         repeater.clear();
120         int initialSize = repeater.getSize();
121         
122         // build a jxpath iterator for pointers
123
JXPathContext repeaterContext =
124             jxpc.getRelativeContext(jxpc.getPointer(this.repeaterPath));
125         Iterator JavaDoc rowPointers = repeaterContext.iteratePointers(this.rowPath);
126         //iterate through it
127
while (rowPointers.hasNext()) {
128             // create a new row, take that as the frmModelSubContext
129
Repeater.RepeaterRow thisRow;
130             if (initialSize > 0) {
131                 thisRow = repeater.getRow(--initialSize);
132             } else {
133                 thisRow = repeater.addRow();
134             }
135             // make a jxpath ObjectModelSubcontext on the iterated element
136
Pointer jxp = (Pointer)rowPointers.next();
137             JXPathContext rowContext = repeaterContext.getRelativeContext(jxp);
138             // hand it over to children
139
if (this.identityBinding != null) {
140                 this.identityBinding.loadFormFromModel(thisRow, rowContext);
141             }
142             this.rowBinding.loadFormFromModel(thisRow, rowContext);
143         }
144         if (getLogger().isDebugEnabled())
145             getLogger().debug("done loading rows " + toString());
146     }
147     
148     /**
149      * Uses the mapped identity of each row to detect if rows have been
150      * updated, inserted or removed. Depending on what happened the appropriate
151      * child-bindings are allowed to visit the narrowed contexts.
152      */

153     public void doSave(Widget frmModel, JXPathContext jxpc)
154     throws BindingException {
155         // Find the repeater
156
Repeater repeater = (Repeater) selectWidget(frmModel, this.repeaterId);
157         // and his context
158
JXPathContext repeaterContext =
159             jxpc.getRelativeContext(jxpc.getPointer(this.repeaterPath));
160         
161         // create set of updatedRowIds
162
Set JavaDoc updatedRows = new HashSet JavaDoc();
163         //create list of rows to insert at end
164
List JavaDoc rowsToInsert = new ArrayList JavaDoc();
165         
166         // iterate rows in the form model...
167
int formRowCount = repeater.getSize();
168         for (int i = 0; i < formRowCount; i++) {
169             Repeater.RepeaterRow thisRow = repeater.getRow(i);
170             
171             // Get the identity
172
List JavaDoc identity = getIdentity(thisRow);
173             
174             if (hasNonNullElements(identity)) {
175                 // iterate nodes to find match
176
Iterator JavaDoc rowPointers = repeaterContext.iteratePointers(this.rowPath);
177                 boolean found = false;
178                 while (rowPointers.hasNext()) {
179                     Pointer jxp = (Pointer) rowPointers.next();
180                     JXPathContext rowContext = repeaterContext.getRelativeContext(jxp);
181                     List JavaDoc contextIdentity = getIdentity(rowContext);
182                     if (ListUtils.isEqualList(identity, contextIdentity)) {
183                         // match! --> bind to children
184
this.rowBinding.saveFormToModel(thisRow, rowContext);
185                         // --> store rowIdValue in list of updatedRowIds
186
updatedRows.add(identity);
187                         found = true;
188                         break;
189                     }
190                 }
191                 if (!found) {
192                     // this is a new row
193
rowsToInsert.add(thisRow);
194                     // also add it to the updated row id's so that this row doesn't get deleted
195
updatedRows.add(identity);
196                 }
197             } else {
198                 // if there is no value to determine the identity --> this is a new row
199
rowsToInsert.add(thisRow);
200             }
201         }
202         // Iterate again nodes for deletion
203
Iterator JavaDoc rowPointers = repeaterContext.iteratePointers(this.rowPath);
204         List JavaDoc rowsToDelete = new ArrayList JavaDoc();
205         while (rowPointers.hasNext()) {
206             Pointer jxp = (Pointer)rowPointers.next();
207             JXPathContext rowContext = repeaterContext.getRelativeContext((Pointer)jxp.clone());
208             List JavaDoc contextIdentity = getIdentity(rowContext);
209             // check if the identity of the rowContext is in the updated rows
210
// if not --> bind for delete
211
if (!isIdentityInUpdatedRows(updatedRows, contextIdentity)) {
212                 rowsToDelete.add(rowContext);
213             }
214         }
215         if (rowsToDelete.size() > 0) {
216             if (this.deleteRowBinding != null) {
217                 // run backwards through the list, so that we don't get into
218
// trouble by shifting indexes
219
for (int i = rowsToDelete.size() - 1; i >= 0; i--) {
220                     this.deleteRowBinding.saveFormToModel(frmModel,
221                             rowsToDelete.get(i));
222                 }
223             } else {
224                 if (getLogger().isWarnEnabled()) {
225                     getLogger().warn(
226                             "RepeaterBinding has detected rows to delete, " +
227                             "but misses the <on-delete-row> binding to do it."
228                     );
229                 }
230             }
231         }
232         // count how many we have now
233
int indexCount = 1;
234         rowPointers = repeaterContext.iteratePointers(this.rowPathForInsert);
235         while (rowPointers.hasNext()) {
236             rowPointers.next();
237             indexCount++;
238         }
239         // end with rows to insert (to make sure they don't get deleted!)
240
if (rowsToInsert.size() > 0) {
241             if (this.insertRowBinding != null) {
242                 Iterator JavaDoc rowIterator = rowsToInsert.iterator();
243                 //register the factory!
244
while (rowIterator.hasNext()) {
245                     Repeater.RepeaterRow thisRow = (Repeater.RepeaterRow)rowIterator.next();
246                     // Perform the insert row binding.
247
this.insertRowBinding.saveFormToModel(repeater, repeaterContext);
248                     // --> create the path to let the context be created
249
Pointer newRowContextPointer = repeaterContext.createPath(
250                             this.rowPathForInsert + "[" + indexCount + "]");
251                     JXPathContext newRowContext =
252                         repeaterContext.getRelativeContext(newRowContextPointer);
253                     if (getLogger().isDebugEnabled()) {
254                         getLogger().debug("inserted row at " + newRowContextPointer.asPath());
255                     }
256                     // + rebind to children for update
257
this.rowBinding.saveFormToModel(thisRow, newRowContext);
258                     getLogger().debug("bound new row");
259                     indexCount++;
260                 }
261             } else {
262                 if (getLogger().isWarnEnabled()) {
263                     getLogger().warn("RepeaterBinding has detected rows to insert, but misses "
264                             + "the <on-insert-row> binding to do it.");
265                 }
266             }
267         }
268         if (getLogger().isDebugEnabled()) {
269             getLogger().debug("done saving rows " + toString());
270         }
271     }
272     
273     /**
274      * Tests if an identity is already contained in a Set of identities.
275      * @param identitySet the Set of identities.
276      * @param identity the identity that is tested if it is already in the Set.
277      * @return true if the Set contains the identity, false otherwise.
278      */

279     private boolean isIdentityInUpdatedRows(Set JavaDoc identitySet, List JavaDoc identity) {
280         Iterator JavaDoc iter = identitySet.iterator();
281         while (iter.hasNext()) {
282             List JavaDoc identityFromSet = (List JavaDoc)iter.next();
283             if (ListUtils.isEqualList(identityFromSet, identity)) {
284                 return true;
285             }
286         }
287         return false;
288     }
289     
290     /**
291      * Tests if any of the elements in a List is not null.
292      * @param list
293      * @return
294      */

295     private boolean hasNonNullElements(List JavaDoc list) {
296         Iterator JavaDoc iter = list.iterator();
297         while (iter.hasNext()) {
298             if (iter.next() != null) {
299                 return true;
300             }
301         }
302         return false;
303     }
304     
305     /**
306      * Get the identity of the given row context. That's infact a list of all
307      * the values of the fields in the bean or XML that constitute the identity.
308      * @param rowContext
309      * @return List the identity of the row context
310      */

311     private List JavaDoc getIdentity(JXPathContext rowContext) {
312         if (this.identityBinding == null) {
313             return Collections.EMPTY_LIST;
314         }
315
316         List JavaDoc identity = new ArrayList JavaDoc();
317         
318         JXPathBindingBase[] childBindings = this.identityBinding.getChildBindings();
319         if (childBindings != null) {
320             int size = childBindings.length;
321             for (int i = 0; i < size; i++) {
322                 ValueJXPathBinding vBinding = (ValueJXPathBinding)childBindings[i];
323                 Object JavaDoc value = rowContext.getValue(vBinding.getXPath());
324                 if (value != null && vBinding.getConvertor() != null) {
325                     if (value instanceof String JavaDoc) {
326                         ConversionResult conversionResult = vBinding.getConvertor().convertFromString(
327                                 (String JavaDoc)value, vBinding.getConvertorLocale(), null);
328                         if (conversionResult.isSuccessful())
329                             value = conversionResult.getResult();
330                         else
331                             value = null;
332                     } else {
333                         if (getLogger().isWarnEnabled()) {
334                             getLogger().warn("Convertor ignored on backend-value " +
335                             "which isn't of type String.");
336                         }
337                     }
338                 }
339                 identity.add(value);
340             }
341         }
342         return identity;
343     }
344     
345     /**
346      * Get the identity of the given row. That's infact a list of all the values
347      * of the fields in the form model that constitute the identity.
348      * @param row
349      * @return List the identity of the row
350      */

351     private List JavaDoc getIdentity(Repeater.RepeaterRow row) {
352         // quit if we don't have an identity binding
353
if (this.identityBinding == null) {
354             return Collections.EMPTY_LIST;
355         }
356         
357         List JavaDoc identity = new ArrayList JavaDoc();
358         
359         JXPathBindingBase[] childBindings = this.identityBinding.getChildBindings();
360         if (childBindings != null) {
361             int size = childBindings.length;
362             for (int i = 0; i < size; i++) {
363                 String JavaDoc fieldId = ((ValueJXPathBinding)childBindings[i]).getFieldId();
364                 Widget widget = row.getChild(fieldId);
365                 Object JavaDoc value = widget.getValue();
366                 identity.add(value);
367             }
368         }
369         return identity;
370     }
371     
372     public String JavaDoc toString() {
373         return "RepeaterJXPathBinding [widget=" + this.repeaterId +
374         ", xpath=" + this.repeaterPath + "]";
375     }
376     
377     public void enableLogging(Logger logger) {
378         super.enableLogging(logger);
379         if (this.deleteRowBinding != null) {
380             this.deleteRowBinding.enableLogging(logger);
381         }
382         if (this.insertRowBinding != null) {
383             this.insertRowBinding.enableLogging(logger);
384         }
385         this.rowBinding.enableLogging(logger);
386         if (this.identityBinding != null) {
387             this.identityBinding.enableLogging(logger);
388         }
389     }
390 }
391
Popular Tags