1 16 package org.apache.cocoon.forms.binding; 17 18 import java.util.ArrayList ; 19 import java.util.Collections ; 20 import java.util.HashSet ; 21 import java.util.Iterator ; 22 import java.util.List ; 23 import java.util.Set ; 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 40 public class RepeaterJXPathBinding extends JXPathBindingBase { 41 42 private final String repeaterId; 43 private final String repeaterPath; 44 private final String rowPath; 45 private final String rowPathForInsert; 46 private final JXPathBindingBase rowBinding; 47 private final JXPathBindingBase insertRowBinding; 48 private final JXPathBindingBase deleteRowBinding; 49 private final ComposedJXPathBindingBase identityBinding; 50 51 54 public RepeaterJXPathBinding( 55 JXPathBindingBuilderBase.CommonAttributes commonAtts, 56 String repeaterId, String repeaterPath, String rowPath, 57 String 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 getId() { return repeaterId; } 98 public String getRepeaterPath() { return repeaterPath; } 99 public String getRowPath() { return rowPath; } 100 public String 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 111 public void doLoad(Widget frmModel, JXPathContext jxpc) 112 throws BindingException { 113 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 JXPathContext repeaterContext = 124 jxpc.getRelativeContext(jxpc.getPointer(this.repeaterPath)); 125 Iterator rowPointers = repeaterContext.iteratePointers(this.rowPath); 126 while (rowPointers.hasNext()) { 128 Repeater.RepeaterRow thisRow; 130 if (initialSize > 0) { 131 thisRow = repeater.getRow(--initialSize); 132 } else { 133 thisRow = repeater.addRow(); 134 } 135 Pointer jxp = (Pointer)rowPointers.next(); 137 JXPathContext rowContext = repeaterContext.getRelativeContext(jxp); 138 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 153 public void doSave(Widget frmModel, JXPathContext jxpc) 154 throws BindingException { 155 Repeater repeater = (Repeater) selectWidget(frmModel, this.repeaterId); 157 JXPathContext repeaterContext = 159 jxpc.getRelativeContext(jxpc.getPointer(this.repeaterPath)); 160 161 Set updatedRows = new HashSet (); 163 List rowsToInsert = new ArrayList (); 165 166 int formRowCount = repeater.getSize(); 168 for (int i = 0; i < formRowCount; i++) { 169 Repeater.RepeaterRow thisRow = repeater.getRow(i); 170 171 List identity = getIdentity(thisRow); 173 174 if (hasNonNullElements(identity)) { 175 Iterator 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 contextIdentity = getIdentity(rowContext); 182 if (ListUtils.isEqualList(identity, contextIdentity)) { 183 this.rowBinding.saveFormToModel(thisRow, rowContext); 185 updatedRows.add(identity); 187 found = true; 188 break; 189 } 190 } 191 if (!found) { 192 rowsToInsert.add(thisRow); 194 updatedRows.add(identity); 196 } 197 } else { 198 rowsToInsert.add(thisRow); 200 } 201 } 202 Iterator rowPointers = repeaterContext.iteratePointers(this.rowPath); 204 List rowsToDelete = new ArrayList (); 205 while (rowPointers.hasNext()) { 206 Pointer jxp = (Pointer)rowPointers.next(); 207 JXPathContext rowContext = repeaterContext.getRelativeContext((Pointer)jxp.clone()); 208 List contextIdentity = getIdentity(rowContext); 209 if (!isIdentityInUpdatedRows(updatedRows, contextIdentity)) { 212 rowsToDelete.add(rowContext); 213 } 214 } 215 if (rowsToDelete.size() > 0) { 216 if (this.deleteRowBinding != null) { 217 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 int indexCount = 1; 234 rowPointers = repeaterContext.iteratePointers(this.rowPathForInsert); 235 while (rowPointers.hasNext()) { 236 rowPointers.next(); 237 indexCount++; 238 } 239 if (rowsToInsert.size() > 0) { 241 if (this.insertRowBinding != null) { 242 Iterator rowIterator = rowsToInsert.iterator(); 243 while (rowIterator.hasNext()) { 245 Repeater.RepeaterRow thisRow = (Repeater.RepeaterRow)rowIterator.next(); 246 this.insertRowBinding.saveFormToModel(repeater, repeaterContext); 248 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 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 279 private boolean isIdentityInUpdatedRows(Set identitySet, List identity) { 280 Iterator iter = identitySet.iterator(); 281 while (iter.hasNext()) { 282 List identityFromSet = (List )iter.next(); 283 if (ListUtils.isEqualList(identityFromSet, identity)) { 284 return true; 285 } 286 } 287 return false; 288 } 289 290 295 private boolean hasNonNullElements(List list) { 296 Iterator iter = list.iterator(); 297 while (iter.hasNext()) { 298 if (iter.next() != null) { 299 return true; 300 } 301 } 302 return false; 303 } 304 305 311 private List getIdentity(JXPathContext rowContext) { 312 if (this.identityBinding == null) { 313 return Collections.EMPTY_LIST; 314 } 315 316 List identity = new ArrayList (); 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 value = rowContext.getValue(vBinding.getXPath()); 324 if (value != null && vBinding.getConvertor() != null) { 325 if (value instanceof String ) { 326 ConversionResult conversionResult = vBinding.getConvertor().convertFromString( 327 (String )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 351 private List getIdentity(Repeater.RepeaterRow row) { 352 if (this.identityBinding == null) { 354 return Collections.EMPTY_LIST; 355 } 356 357 List identity = new ArrayList (); 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 fieldId = ((ValueJXPathBinding)childBindings[i]).getFieldId(); 364 Widget widget = row.getChild(fieldId); 365 Object value = widget.getValue(); 366 identity.add(value); 367 } 368 } 369 return identity; 370 } 371 372 public String 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 |