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.forms.binding;
018:
019: import java.util.Iterator;
020:
021: import org.apache.avalon.framework.logger.Logger;
022:
023: import org.apache.cocoon.forms.formmodel.Repeater;
024: import org.apache.cocoon.forms.formmodel.Widget;
025:
026: import org.apache.commons.jxpath.JXPathContext;
027: import org.apache.commons.jxpath.Pointer;
028: import org.w3c.dom.Document;
029: import org.w3c.dom.Node;
030: import org.w3c.dom.NodeList;
031:
032: /**
033: * Experimental simple binding for repeaters:
034: * on save, first deletes the target data before recreating it from scratch.
035: * Based on code from SimpleRepeater.
036: * <p>
037: * For a smarter binding that avoids deletion and recreation, consider
038: * {@link org.apache.cocoon.forms.binding.RepeaterJXPathBinding}
039: *
040: * @version $Id: TempRepeaterJXPathBinding.java 517733 2007-03-13 15:37:22Z vgritsenko $
041: */
042: public class TempRepeaterJXPathBinding extends JXPathBindingBase {
043:
044: private final String repeaterId;
045: private final String repeaterPath;
046: private final String rowPath;
047: private final String rowPathInsert;
048: private final boolean clearOnLoad;
049: private final JXPathBindingBase rowBinding;
050: private final JXPathBindingBase insertRowBinding;
051: private final boolean deleteIfEmpty;
052: private final boolean virtualRows;
053:
054: public TempRepeaterJXPathBinding(
055: JXPathBindingBuilderBase.CommonAttributes commonAtts,
056: String repeaterId, String repeaterPath, String rowPath,
057: String rowPathInsert, boolean virtualRows,
058: boolean clearOnLoad, boolean deleteIfEmpty,
059: JXPathBindingBase rowBinding,
060: JXPathBindingBase insertBinding) {
061: super (commonAtts);
062: this .repeaterId = repeaterId;
063: this .repeaterPath = repeaterPath;
064: this .rowPath = rowPath;
065: this .rowPathInsert = rowPathInsert;
066: this .rowBinding = rowBinding;
067: this .rowBinding.setParent(this );
068: this .insertRowBinding = insertBinding;
069: this .insertRowBinding.setParent(this );
070: this .virtualRows = virtualRows;
071: this .clearOnLoad = clearOnLoad;
072: this .deleteIfEmpty = deleteIfEmpty;
073: }
074:
075: public void enableLogging(Logger logger) {
076: super .enableLogging(logger);
077: if (this .insertRowBinding != null) {
078: this .insertRowBinding.enableLogging(logger);
079: }
080: this .rowBinding.enableLogging(logger);
081: }
082:
083: public String getId() {
084: return repeaterId;
085: }
086:
087: public String getRepeaterPath() {
088: return repeaterPath;
089: }
090:
091: public String getRowPath() {
092: return rowPath;
093: }
094:
095: public String getRowPathInsert() {
096: return rowPathInsert;
097: }
098:
099: public boolean getVirtualRows() {
100: return virtualRows;
101: }
102:
103: public boolean getClearOnLoad() {
104: return clearOnLoad;
105: }
106:
107: public boolean getDeleteIfEmpty() {
108: return deleteIfEmpty;
109: }
110:
111: public JXPathBindingBase[] getChildBindings() {
112: return ((ComposedJXPathBindingBase) rowBinding)
113: .getChildBindings();
114: }
115:
116: public JXPathBindingBase[] getInsertChildBindings() {
117: return ((ComposedJXPathBindingBase) insertRowBinding)
118: .getChildBindings();
119: }
120:
121: public void doLoad(Widget frmModel, JXPathContext jctx)
122: throws BindingException {
123: // (There should be a general widget type checker for all the bindings to use,
124: // coupled with a general informative exception class to throw if the widget is
125: // of the wrong type or null.)
126: Repeater repeater = (Repeater) selectWidget(frmModel,
127: this .repeaterId);
128: if (repeater == null) {
129: String fullId = frmModel.getRequestParameterName();
130: if (fullId == null || fullId.length() == 0) {
131: fullId = "";
132: } else {
133: fullId = fullId + ".";
134: }
135: throw new RuntimeException(
136: "TempRepeaterJXPathBinding: Repeater \"" + fullId
137: + this .repeaterId + "\" does not exist ("
138: + frmModel.getLocation() + ")");
139: }
140:
141: // Start by clearing the repeater, if necessary.
142: if (this .clearOnLoad) {
143: repeater.clear();
144: }
145:
146: // Find the location of the repeater data.
147: Pointer repeaterPointer = jctx.getPointer(this .repeaterPath);
148:
149: // Check if there is data present.
150: //
151: // (Otherwise, should we check the leniency config option
152: // to decide whether to be silent or throw an exception?)
153: if (repeaterPointer != null) {
154:
155: // Narrow to repeater context.
156: JXPathContext repeaterContext = jctx
157: .getRelativeContext(repeaterPointer);
158:
159: // Build a jxpath iterator for the repeater row pointers.
160: Iterator rowPointers = repeaterContext
161: .iteratePointers(this .rowPath);
162:
163: // Iterate through the rows of data.
164: int rowNum = 0;
165: while (rowPointers.hasNext()) {
166:
167: // Get or create a row widget.
168: Repeater.RepeaterRow this Row;
169: if (repeater.getSize() > rowNum) {
170: this Row = repeater.getRow(rowNum);
171: } else {
172: this Row = repeater.addRow();
173: }
174: rowNum++;
175:
176: // Narrow to the row context.
177: Pointer rowPointer = (Pointer) rowPointers.next();
178: JXPathContext rowContext = repeaterContext
179: .getRelativeContext(rowPointer);
180:
181: // If virtual rows are requested, place a deep clone of the row data
182: // into a temporary node, and narrow the context to this virtual row.
183: //
184: // (A clone of the data is used to prevent modifying the source document.
185: // Otherwise, the appendChild method would remove the data from the source
186: // document. Is this protection worth the penalty of a deep clone?)
187: //
188: // (This implementation of virtual rows currently only supports DOM
189: // bindings, but could easily be extended to support other bindings.)
190:
191: if (virtualRows) {
192: Node repeaterNode = (Node) repeaterPointer
193: .getNode();
194: Node virtualNode = repeaterNode.getOwnerDocument()
195: .createElementNS(null, "virtual");
196: Node node = (Node) rowPointer.getNode();
197: Node clone = node.cloneNode(true);
198: Node fakeDocElement = node.getOwnerDocument()
199: .getDocumentElement().cloneNode(false);
200: virtualNode.appendChild(clone);
201: fakeDocElement.appendChild(virtualNode);
202: rowContext = JXPathContext.newContext(
203: repeaterContext, fakeDocElement);
204: rowContext = rowContext
205: .getRelativeContext(rowContext
206: .getPointer("virtual"));
207: }
208:
209: // Finally, perform the load row binding.
210: this .rowBinding.loadFormFromModel(this Row, rowContext);
211: }
212: }
213:
214: if (getLogger().isDebugEnabled())
215: getLogger().debug("done loading rows " + this );
216: }
217:
218: public void doSave(Widget frmModel, JXPathContext jctx)
219: throws BindingException {
220: // (See comment in doLoad about type checking and throwing a meaningful exception.)
221: Repeater repeater = (Repeater) selectWidget(frmModel,
222: this .repeaterId);
223:
224: // Perform shortcut binding if the repeater is empty
225: // and the deleteIfEmpty config option is selected.
226: if (repeater.getSize() == 0 && this .deleteIfEmpty) {
227: // Delete all of the old data for this repeater.
228: jctx.removeAll(this .repeaterPath);
229:
230: // Otherwise perform the normal save binding.
231: } else {
232:
233: // Narrow to the repeater context, creating the path if it did not exist.
234: JXPathContext repeaterContext = jctx
235: .getRelativeContext(jctx
236: .createPath(this .repeaterPath));
237:
238: // Start by deleting all of the old row data.
239: repeaterContext.removeAll(this .rowPath);
240:
241: // Verify that repeater is not empty and has an insert row binding.
242: if (repeater.getSize() > 0) {
243: if (this .insertRowBinding != null) {
244:
245: //register the factory!
246: //this.insertRowBinding.saveFormToModel(repeater, repeaterContext);
247:
248: // Iterate through the repeater rows.
249: for (int i = 0; i < repeater.getSize(); i++) {
250:
251: // Narrow to the repeater row context.
252: Pointer rowPointer = repeaterContext
253: .getPointer(this .rowPathInsert);
254: JXPathContext rowContext = repeaterContext
255: .getRelativeContext(rowPointer);
256:
257: // Variables used for virtual rows.
258: // They are initialized here just to keep the compiler happy.
259: Node rowNode = null;
260: Node virtualNode = null;
261:
262: // If virtual rows are requested, create a temporary node and
263: // narrow the context to this initially empty new virtual row.
264: if (virtualRows) {
265: rowNode = (Node) rowContext
266: .getContextBean();
267: Document document = rowNode
268: .getOwnerDocument();
269: virtualNode = document.createElementNS(
270: null, "virtual");
271: Node fakeDocElement = document
272: .getDocumentElement().cloneNode(
273: false);
274: fakeDocElement.appendChild(virtualNode);
275: rowContext = JXPathContext.newContext(
276: repeaterContext, fakeDocElement);
277: rowContext = rowContext
278: .getRelativeContext(rowContext
279: .getPointer("virtual"));
280: }
281:
282: // Perform the insert row binding
283: this .insertRowBinding.saveFormToModel(repeater,
284: rowContext);
285:
286: // Perform the save row binding.
287: this .rowBinding.saveFormToModel(repeater
288: .getRow(i), rowContext);
289:
290: // If virtual rows are requested, finish by appending the
291: // children of the virtual row to the real context node.
292: if (virtualRows) {
293: NodeList list = virtualNode.getChildNodes();
294: int count = list.getLength();
295: for (int j = 0; j < count; j++) {
296: // The list shrinks when a child is appended to the context
297: // node, so we always reference the first child in the list.
298: rowNode.appendChild(list.item(0));
299: }
300: }
301: getLogger().debug("bound new row");
302: }
303: } else {
304: getLogger()
305: .warn(
306: "TempRepeaterBinding has detected rows to insert, "
307: + "but misses the <on-insert-row> binding to do it.");
308: }
309: }
310: }
311: }
312:
313: public String toString() {
314: return "TempRepeaterJXPathBinding [widget=" + this .repeaterId
315: + ", xpath=" + this .repeaterPath + "]";
316: }
317: }
|