KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > woody > 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.woody.binding;
17
18 import java.util.ArrayList JavaDoc;
19 import java.util.HashSet JavaDoc;
20 import java.util.Iterator JavaDoc;
21 import java.util.List JavaDoc;
22 import java.util.Locale JavaDoc;
23 import java.util.Set JavaDoc;
24
25 import org.apache.avalon.framework.logger.Logger;
26 import org.apache.cocoon.woody.datatype.convertor.Convertor;
27 import org.apache.cocoon.woody.formmodel.Widget;
28 import org.apache.cocoon.woody.formmodel.Repeater;
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 CVS $Id: RepeaterJXPathBinding.java 30932 2004-07-29 17:35:38Z vgritsenko $
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 List JavaDoc uniqueRowBinding;
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, String JavaDoc uniqueRowId,
58             String JavaDoc uniqueRowPath, JXPathBindingBase[] childBindings,
59             JXPathBindingBase insertBinding,
60             JXPathBindingBase[] deleteBindings, JXPathBindingBase[] uniqueBindings) {
61         this(commonAtts, repeaterId, repeaterPath, rowPath, rowPathForInsert,
62                 uniqueRowId, uniqueRowPath, null, null, childBindings,
63                 insertBinding, deleteBindings, uniqueBindings);
64     }
65
66     /**
67      * Constructs RepeaterJXPathBinding
68      */

69     public RepeaterJXPathBinding(
70             JXPathBindingBuilderBase.CommonAttributes commonAtts,
71             String JavaDoc repeaterId, String JavaDoc repeaterPath, String JavaDoc rowPath,
72             String JavaDoc rowPathForInsert, String JavaDoc uniqueRowId,
73             String JavaDoc uniqueRowPath, Convertor convertor, Locale JavaDoc convertorLocale,
74             JXPathBindingBase[] childBindings, JXPathBindingBase insertBinding,
75             JXPathBindingBase[] deleteBindings, JXPathBindingBase[] uniqueBindings) {
76         super(commonAtts);
77         this.repeaterId = repeaterId;
78         this.repeaterPath = repeaterPath;
79         this.rowPath = rowPath;
80         this.rowPathForInsert = rowPathForInsert;
81         this.rowBinding = new ComposedJXPathBindingBase(
82                 JXPathBindingBuilderBase.CommonAttributes.DEFAULT,
83                 childBindings);
84         this.rowBinding.setParent(this);
85         this.insertRowBinding = insertBinding;
86         if (this.insertRowBinding != null) {
87             this.insertRowBinding.setParent(this);
88         }
89
90         if (deleteBindings != null) {
91             this.deleteRowBinding = new ComposedJXPathBindingBase(
92                     JXPathBindingBuilderBase.CommonAttributes.DEFAULT,
93                     deleteBindings);
94             this.deleteRowBinding.setParent(this);
95         } else {
96             this.deleteRowBinding = null;
97         }
98
99         // New unique key management
100
uniqueRowBinding = new ArrayList JavaDoc();
101         // Create a UniqueFieldJXPathBining for the unique define in old-style
102
if (uniqueRowId != null && uniqueRowPath != null) {
103             uniqueRowBinding.add(new UniqueFieldJXPathBinding(
104                 JXPathBindingBuilderBase.CommonAttributes.DEFAULT,
105                 uniqueRowId, uniqueRowPath, convertor, convertorLocale));
106         }
107         if (uniqueBindings != null) {
108             for (int i=0; i < uniqueBindings.length; i++) {
109                 uniqueRowBinding.add(uniqueBindings[i]);
110             }
111         }
112     }
113
114     /**
115      * Binds the unique-id of the repeated rows, and narrows the context on
116      * objectModelContext and Repeater to the repeated rows before handing
117      * over to the actual binding-children.
118      */

119     public void doLoad(Widget frmModel, JXPathContext jxpc)
120             throws BindingException {
121         // Find the repeater
122
Repeater repeater = (Repeater) frmModel.getWidget(this.repeaterId);
123         repeater.removeRows();
124         int initialSize = repeater.getSize();
125
126         // build a jxpath iterator for pointers
127
JXPathContext repeaterContext =
128             jxpc.getRelativeContext(jxpc.getPointer(this.repeaterPath));
129         Iterator JavaDoc rowPointers = repeaterContext.iteratePointers(this.rowPath);
130         //iterate through it
131
while (rowPointers.hasNext()) {
132             // create a new row, take that as the frmModelSubContext
133
Repeater.RepeaterRow thisRow;
134             if (initialSize > 0) {
135                 thisRow = repeater.getRow(--initialSize);
136             } else {
137                 thisRow = repeater.addRow();
138             }
139             // make a jxpath ObjectModelSubcontext on the iterated element
140
Pointer jxp = (Pointer)rowPointers.next();
141             JXPathContext rowContext = repeaterContext.getRelativeContext(jxp);
142             // hand it over to children
143
Iterator JavaDoc iter = this.uniqueRowBinding.iterator();
144             while (iter.hasNext()) {
145                 ((UniqueFieldJXPathBinding)iter.next()).loadFormFromModel(thisRow, rowContext);
146             }
147             this.rowBinding.loadFormFromModel(thisRow, rowContext);
148         }
149         if (getLogger().isDebugEnabled())
150             getLogger().debug("done loading rows " + toString());
151     }
152
153     /**
154      * Uses the mapped unique-id of each row to detect if rows have been
155      * updated, inserted or removed. Depending on what happened the appropriate
156      * child-bindings are alowed to visit the narrowed contexts.
157      */

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

283     private boolean isListInSet(Set JavaDoc set, List JavaDoc list) {
284         Iterator JavaDoc iter = set.iterator();
285         while (iter.hasNext()) {
286             List JavaDoc listFromSet = (List JavaDoc)iter.next();
287             if (ListUtils.isEqualList(listFromSet, list)) {
288                 return true;
289             }
290         }
291         return false;
292     }
293
294     /**
295      * Tests if any of the elements in a List is not null.
296      * @param list
297      * @return
298      */

299     private boolean isAnyListElementNotNull(List JavaDoc list) {
300         Iterator JavaDoc iter = list.iterator();
301         while (iter.hasNext()) {
302             if (iter.next() != null) {
303                 return true;
304             }
305         }
306         return false;
307     }
308
309     /**
310      *
311      * @param rowContext
312      * @return
313      */

314     private List JavaDoc getMatchIds(JXPathContext rowContext) {
315         List JavaDoc matchIds = new ArrayList JavaDoc();
316         Iterator JavaDoc iter = this.uniqueRowBinding.iterator();
317         while (iter.hasNext()) {
318             UniqueFieldJXPathBinding key = (UniqueFieldJXPathBinding)iter.next();
319             Object JavaDoc matchId = rowContext.getValue(key.getXpath());
320             if (matchId != null && key.getConvertor() != null) {
321                 if (matchId instanceof String JavaDoc) {
322                     matchId = key.getConvertor().convertFromString(
323                             (String JavaDoc)matchId, key.getConvertorLocale(), null);
324                 } else {
325                     if (getLogger().isWarnEnabled()) {
326                         getLogger().warn("Convertor ignored on backend-value " +
327                                 "which isn't of type String.");
328                     }
329                 }
330             }
331             matchIds.add(matchId);
332         }
333         return matchIds;
334     }
335
336     /**
337      * Get the values of the unique-fields of the given row in the formModel
338      * @param thisRow
339      * @return List
340      */

341     private List JavaDoc getUniqueRowValues(Repeater.RepeaterRow thisRow) {
342         List JavaDoc values = new ArrayList JavaDoc();
343         Iterator JavaDoc iter = this.uniqueRowBinding.iterator();
344         while (iter.hasNext()) {
345             UniqueFieldJXPathBinding key = (UniqueFieldJXPathBinding)iter.next();
346             Widget rowIdWidget = thisRow.getWidget(key.getFieldId());
347             Object JavaDoc rowIdValue = rowIdWidget.getValue();
348             values.add(rowIdValue);
349         }
350         return values;
351     }
352
353     public String JavaDoc toString() {
354         return "RepeaterJXPathBinding [widget=" + this.repeaterId +
355                ", xpath=" + this.repeaterPath + "]";
356     }
357
358     public void enableLogging(Logger logger) {
359         super.enableLogging(logger);
360         if (this.deleteRowBinding != null) {
361             this.deleteRowBinding.enableLogging(logger);
362         }
363         if (this.insertRowBinding != null) {
364             this.insertRowBinding.enableLogging(logger);
365         }
366         this.rowBinding.enableLogging(logger);
367         Iterator JavaDoc iter = this.uniqueRowBinding.iterator();
368         while (iter.hasNext()) {
369             ((UniqueFieldJXPathBinding)iter.next()).enableLogging(logger);
370         }
371     }
372 }
373
Popular Tags