blob: 49b8698774e80b2e5469b248c23e5808bdca1648 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.woody.binding;
import java.util.Iterator;
import org.apache.avalon.framework.logger.Logger;
import org.apache.cocoon.woody.formmodel.Repeater;
import org.apache.cocoon.woody.formmodel.Widget;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.Pointer;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Experimental simple binding for repeaters:
* on save, first deletes the target data before recreating it from scratch.
* Based on code from SimpleRepeater.
* <p>
* For a smarter binding that avoids deletion and recreation, consider
* {@link org.apache.cocoon.woody.binding.RepeaterJXPathBinding}
*
* @author Timothy Larson
* @version CVS $Id$
*/
public class TempRepeaterJXPathBinding extends JXPathBindingBase {
private final String repeaterId;
private final String repeaterPath;
private final String rowPath;
private final String rowPathInsert;
private final boolean clearOnLoad;
private final JXPathBindingBase rowBinding;
private final JXPathBindingBase insertRowBinding;
private final boolean deleteIfEmpty;
private final boolean virtualRows;
public TempRepeaterJXPathBinding(
JXPathBindingBuilderBase.CommonAttributes commonAtts,
String repeaterId, String repeaterPath,
String rowPath, String rowPathInsert,
boolean virtualRows, boolean clearOnLoad, boolean deleteIfEmpty,
JXPathBindingBase rowBinding, JXPathBindingBase insertBinding) {
super(commonAtts);
this.repeaterId = repeaterId;
this.repeaterPath = repeaterPath;
this.rowPath = rowPath;
this.rowPathInsert = rowPathInsert;
this.rowBinding = rowBinding;
this.rowBinding.setParent(this);
this.insertRowBinding = insertBinding;
this.insertRowBinding.setParent(this);
this.virtualRows = virtualRows;
this.clearOnLoad = clearOnLoad;
this.deleteIfEmpty = deleteIfEmpty;
}
public void doLoad(Widget frmModel, JXPathContext jctx) throws BindingException {
// (There should be a general widget type checker for all the bindings to use,
// coupled with a general informative exception class to throw if the widget is
// of the wrong type or null.)
Repeater repeater = (Repeater) frmModel.getWidget(this.repeaterId);
if (repeater == null) {
String fullId = frmModel.getFullyQualifiedId();
if (fullId == null || fullId.length() == 0) {
fullId = "";
} else {
fullId = fullId + ".";
}
throw new RuntimeException(
"TempRepeaterJXPathBinding: Repeater \"" + fullId + this.repeaterId +
"\" does not exist (" + frmModel.getLocation() + ")");
}
// Start by clearing the repeater, if necessary.
if (this.clearOnLoad) {
repeater.removeRows();
}
// Find the location of the repeater data.
Pointer repeaterPointer = jctx.getPointer(this.repeaterPath);
// Check if there is data present.
//
// (Otherwise, should we check the leniency config option
// to decide whether to be silent or throw an exception?)
if (repeaterPointer != null) {
// Narrow to repeater context.
JXPathContext repeaterContext = jctx.getRelativeContext(repeaterPointer);
// Build a jxpath iterator for the repeater row pointers.
Iterator rowPointers = repeaterContext.iteratePointers(this.rowPath);
// Iterate through the rows of data.
int rowNum = 0;
while (rowPointers.hasNext()) {
// Get or create a row widget.
Repeater.RepeaterRow thisRow;
if (repeater.getSize() > rowNum) {
thisRow = repeater.getRow(rowNum);
} else {
thisRow = repeater.addRow();
}
rowNum++;
// Narrow to the row context.
Pointer rowPointer = (Pointer) rowPointers.next();
JXPathContext rowContext = repeaterContext.getRelativeContext(rowPointer);
// If virtual rows are requested, place a deep clone of the row data
// into a temporary node, and narrow the context to this virtual row.
//
// (A clone of the data is used to prevent modifying the source document.
// Otherwise, the appendChild method would remove the data from the source
// document. Is this protection worth the penalty of a deep clone?)
//
// (This implementation of virtual rows currently only supports DOM
// bindings, but could easily be extended to support other bindings.)
if (virtualRows == true) {
Node repeaterNode = (Node)repeaterPointer.getNode();
Node virtualNode = repeaterNode.getOwnerDocument().createElementNS(null, "virtual");
Node clone = ((Node)rowPointer.getNode()).cloneNode(true);
virtualNode.appendChild(clone);
rowContext = JXPathContext.newContext(repeaterContext, virtualNode);
}
// Finally, perform the load row binding.
this.rowBinding.loadFormFromModel(thisRow, rowContext);
}
}
if (getLogger().isDebugEnabled())
getLogger().debug("done loading rows " + toString());
}
public void doSave(Widget frmModel, JXPathContext jctx) throws BindingException {
// (See comment in doLoad about type checking and throwing a meaningful exception.)
Repeater repeater = (Repeater) frmModel.getWidget(this.repeaterId);
// Perform shortcut binding if the repeater is empty
// and the deleteIfEmpty config option is selected.
if (repeater.getSize() == 0 && this.deleteIfEmpty) {
// Delete all of the old data for this repeater.
jctx.removeAll(this.repeaterPath);
// Otherwise perform the normal save binding.
} else {
// Narrow to the repeater context, creating the path if it did not exist.
JXPathContext repeaterContext = jctx.getRelativeContext(jctx.createPath(this.repeaterPath));
// Start by deleting all of the old row data.
repeaterContext.removeAll(this.rowPath);
// Verify that repeater is not empty and has an insert row binding.
if(repeater.getSize() > 0) {
if (this.insertRowBinding != null) {
//register the factory!
//this.insertRowBinding.saveFormToModel(repeater, repeaterContext);
// Iterate through the repeater rows.
for (int i = 0; i < repeater.getSize(); i++) {
// Narrow to the repeater row context.
Pointer rowPointer = repeaterContext.getPointer(this.rowPathInsert);
JXPathContext rowContext = repeaterContext.getRelativeContext(rowPointer);
// Variables used for virtual rows.
// They are initialized here just to keep the compiler happy.
Node rowNode = null;
Node virtualNode = null;
// If virtual rows are requested, create a temporary node and
// narrow the context to this initially empty new virtual row.
if (virtualRows == true) {
rowNode = (Node)rowContext.getContextBean();
virtualNode = rowNode.getOwnerDocument().createElementNS(null, "virtual");
rowContext = JXPathContext.newContext(repeaterContext, virtualNode);
}
// Perform the insert row binding
this.insertRowBinding.saveFormToModel(repeater, rowContext);
// Perform the save row binding.
this.rowBinding.saveFormToModel(repeater.getRow(i), rowContext);
// If virtual rows are requested, finish by appending the
// children of the virtual row to the real context node.
if (virtualRows == true) {
NodeList list = virtualNode.getChildNodes();
int count = list.getLength();
for (int j = 0; j < count; j++) {
// The list shrinks when a child is appended to the context
// node, so we always reference the first child in the list.
rowNode.appendChild(list.item(0));
}
}
getLogger().debug("bound new row");
}
} else {
getLogger().warn("TempRepeaterBinding has detected rows to insert, " +
"but misses the <on-insert-row> binding to do it.");
}
}
}
}
public String toString() {
return "TempRepeaterJXPathBinding [widget=" + this.repeaterId + ", xpath=" + this.repeaterPath + "]";
}
public void enableLogging(Logger logger) {
super.enableLogging(logger);
if (this.insertRowBinding != null) {
this.insertRowBinding.enableLogging(logger);
}
this.rowBinding.enableLogging(logger);
}
}