001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.woody.binding;
018:
019: import java.util.Iterator;
020:
021: import org.apache.avalon.framework.logger.Logger;
022: import org.apache.cocoon.woody.formmodel.Repeater;
023: import org.apache.cocoon.woody.formmodel.Widget;
024: import org.apache.commons.jxpath.JXPathContext;
025: import org.apache.commons.jxpath.Pointer;
026: import org.w3c.dom.Node;
027: import org.w3c.dom.NodeList;
028:
029: /**
030: * Experimental simple binding for repeaters:
031: * on save, first deletes the target data before recreating it from scratch.
032: * Based on code from SimpleRepeater.
033: * <p>
034: * For a smarter binding that avoids deletion and recreation, consider
035: * {@link org.apache.cocoon.woody.binding.RepeaterJXPathBinding}
036: *
037: * @author Timothy Larson
038: * @version CVS $Id: TempRepeaterJXPathBinding.java 433543 2006-08-22 06:22:54Z crossley $
039: */
040: public class TempRepeaterJXPathBinding extends JXPathBindingBase {
041:
042: private final String repeaterId;
043: private final String repeaterPath;
044: private final String rowPath;
045: private final String rowPathInsert;
046: private final boolean clearOnLoad;
047: private final JXPathBindingBase rowBinding;
048: private final JXPathBindingBase insertRowBinding;
049: private final boolean deleteIfEmpty;
050: private final boolean virtualRows;
051:
052: public TempRepeaterJXPathBinding(
053: JXPathBindingBuilderBase.CommonAttributes commonAtts,
054: String repeaterId, String repeaterPath, String rowPath,
055: String rowPathInsert, boolean virtualRows,
056: boolean clearOnLoad, boolean deleteIfEmpty,
057: JXPathBindingBase rowBinding,
058: JXPathBindingBase insertBinding) {
059: super (commonAtts);
060: this .repeaterId = repeaterId;
061: this .repeaterPath = repeaterPath;
062: this .rowPath = rowPath;
063: this .rowPathInsert = rowPathInsert;
064: this .rowBinding = rowBinding;
065: this .rowBinding.setParent(this );
066: this .insertRowBinding = insertBinding;
067: this .insertRowBinding.setParent(this );
068: this .virtualRows = virtualRows;
069: this .clearOnLoad = clearOnLoad;
070: this .deleteIfEmpty = deleteIfEmpty;
071: }
072:
073: public void doLoad(Widget frmModel, JXPathContext jctx)
074: throws BindingException {
075: // (There should be a general widget type checker for all the bindings to use,
076: // coupled with a general informative exception class to throw if the widget is
077: // of the wrong type or null.)
078: Repeater repeater = (Repeater) frmModel
079: .getWidget(this .repeaterId);
080: if (repeater == null) {
081: String fullId = frmModel.getFullyQualifiedId();
082: if (fullId == null || fullId.length() == 0) {
083: fullId = "";
084: } else {
085: fullId = fullId + ".";
086: }
087: throw new RuntimeException(
088: "TempRepeaterJXPathBinding: Repeater \"" + fullId
089: + this .repeaterId + "\" does not exist ("
090: + frmModel.getLocation() + ")");
091: }
092:
093: // Start by clearing the repeater, if necessary.
094: if (this .clearOnLoad) {
095: repeater.removeRows();
096: }
097:
098: // Find the location of the repeater data.
099: Pointer repeaterPointer = jctx.getPointer(this .repeaterPath);
100:
101: // Check if there is data present.
102: //
103: // (Otherwise, should we check the leniency config option
104: // to decide whether to be silent or throw an exception?)
105: if (repeaterPointer != null) {
106:
107: // Narrow to repeater context.
108: JXPathContext repeaterContext = jctx
109: .getRelativeContext(repeaterPointer);
110:
111: // Build a jxpath iterator for the repeater row pointers.
112: Iterator rowPointers = repeaterContext
113: .iteratePointers(this .rowPath);
114:
115: // Iterate through the rows of data.
116: int rowNum = 0;
117: while (rowPointers.hasNext()) {
118:
119: // Get or create a row widget.
120: Repeater.RepeaterRow this Row;
121: if (repeater.getSize() > rowNum) {
122: this Row = repeater.getRow(rowNum);
123: } else {
124: this Row = repeater.addRow();
125: }
126: rowNum++;
127:
128: // Narrow to the row context.
129: Pointer rowPointer = (Pointer) rowPointers.next();
130: JXPathContext rowContext = repeaterContext
131: .getRelativeContext(rowPointer);
132:
133: // If virtual rows are requested, place a deep clone of the row data
134: // into a temporary node, and narrow the context to this virtual row.
135: //
136: // (A clone of the data is used to prevent modifying the source document.
137: // Otherwise, the appendChild method would remove the data from the source
138: // document. Is this protection worth the penalty of a deep clone?)
139: //
140: // (This implementation of virtual rows currently only supports DOM
141: // bindings, but could easily be extended to support other bindings.)
142:
143: if (virtualRows == true) {
144: Node repeaterNode = (Node) repeaterPointer
145: .getNode();
146: Node virtualNode = repeaterNode.getOwnerDocument()
147: .createElementNS(null, "virtual");
148: Node clone = ((Node) rowPointer.getNode())
149: .cloneNode(true);
150: virtualNode.appendChild(clone);
151: rowContext = JXPathContext.newContext(
152: repeaterContext, virtualNode);
153: }
154:
155: // Finally, perform the load row binding.
156: this .rowBinding.loadFormFromModel(this Row, rowContext);
157: }
158: }
159:
160: if (getLogger().isDebugEnabled())
161: getLogger().debug("done loading rows " + toString());
162: }
163:
164: public void doSave(Widget frmModel, JXPathContext jctx)
165: throws BindingException {
166: // (See comment in doLoad about type checking and throwing a meaningful exception.)
167: Repeater repeater = (Repeater) frmModel
168: .getWidget(this .repeaterId);
169:
170: // Perform shortcut binding if the repeater is empty
171: // and the deleteIfEmpty config option is selected.
172: if (repeater.getSize() == 0 && this .deleteIfEmpty) {
173: // Delete all of the old data for this repeater.
174: jctx.removeAll(this .repeaterPath);
175:
176: // Otherwise perform the normal save binding.
177: } else {
178:
179: // Narrow to the repeater context, creating the path if it did not exist.
180: JXPathContext repeaterContext = jctx
181: .getRelativeContext(jctx
182: .createPath(this .repeaterPath));
183:
184: // Start by deleting all of the old row data.
185: repeaterContext.removeAll(this .rowPath);
186:
187: // Verify that repeater is not empty and has an insert row binding.
188: if (repeater.getSize() > 0) {
189: if (this .insertRowBinding != null) {
190:
191: //register the factory!
192: //this.insertRowBinding.saveFormToModel(repeater, repeaterContext);
193:
194: // Iterate through the repeater rows.
195: for (int i = 0; i < repeater.getSize(); i++) {
196:
197: // Narrow to the repeater row context.
198: Pointer rowPointer = repeaterContext
199: .getPointer(this .rowPathInsert);
200: JXPathContext rowContext = repeaterContext
201: .getRelativeContext(rowPointer);
202:
203: // Variables used for virtual rows.
204: // They are initialized here just to keep the compiler happy.
205: Node rowNode = null;
206: Node virtualNode = null;
207:
208: // If virtual rows are requested, create a temporary node and
209: // narrow the context to this initially empty new virtual row.
210: if (virtualRows == true) {
211: rowNode = (Node) rowContext
212: .getContextBean();
213: virtualNode = rowNode.getOwnerDocument()
214: .createElementNS(null, "virtual");
215: rowContext = JXPathContext.newContext(
216: repeaterContext, virtualNode);
217: }
218:
219: // Perform the insert row binding
220: this .insertRowBinding.saveFormToModel(repeater,
221: rowContext);
222:
223: // Perform the save row binding.
224: this .rowBinding.saveFormToModel(repeater
225: .getRow(i), rowContext);
226:
227: // If virtual rows are requested, finish by appending the
228: // children of the virtual row to the real context node.
229: if (virtualRows == true) {
230: NodeList list = virtualNode.getChildNodes();
231: int count = list.getLength();
232: for (int j = 0; j < count; j++) {
233: // The list shrinks when a child is appended to the context
234: // node, so we always reference the first child in the list.
235: rowNode.appendChild(list.item(0));
236: }
237: }
238: getLogger().debug("bound new row");
239: }
240: } else {
241: getLogger()
242: .warn(
243: "TempRepeaterBinding has detected rows to insert, "
244: + "but misses the <on-insert-row> binding to do it.");
245: }
246: }
247: }
248: }
249:
250: public String toString() {
251: return "TempRepeaterJXPathBinding [widget=" + this .repeaterId
252: + ", xpath=" + this .repeaterPath + "]";
253: }
254:
255: public void enableLogging(Logger logger) {
256: super.enableLogging(logger);
257: if (this.insertRowBinding != null) {
258: this.insertRowBinding.enableLogging(logger);
259: }
260: this.rowBinding.enableLogging(logger);
261: }
262: }
|