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.util;
018:
019: import java.util.Iterator;
020: import java.util.Locale;
021:
022: import org.apache.cocoon.xml.AbstractXMLConsumer;
023: import org.apache.cocoon.forms.datatype.Datatype;
024: import org.apache.cocoon.forms.datatype.convertor.ConversionResult;
025: import org.apache.cocoon.forms.formmodel.Action;
026: import org.apache.cocoon.forms.formmodel.AggregateField;
027: import org.apache.cocoon.forms.formmodel.BooleanField;
028: import org.apache.cocoon.forms.formmodel.ContainerWidget;
029: import org.apache.cocoon.forms.formmodel.DataWidget;
030: import org.apache.cocoon.forms.formmodel.Form;
031: import org.apache.cocoon.forms.formmodel.MultiValueField;
032: import org.apache.cocoon.forms.formmodel.Repeater;
033: import org.apache.cocoon.forms.formmodel.Widget;
034: import org.apache.excalibur.xml.sax.XMLizable;
035: import org.xml.sax.Attributes;
036: import org.xml.sax.ContentHandler;
037: import org.xml.sax.SAXException;
038: import org.xml.sax.helpers.AttributesImpl;
039:
040: /**
041: * Adapter class that wraps a <code>Form</code> object and makes it
042: * possible to populate a widget hierarchy from XML in form of SAX
043: * events and serialize the content of the widget hierarchy as XML.
044: *
045: * <p>The XML format is such that there is one XML element for each
046: * widget and the element get the widgets id as name. Exceptions from
047: * this is that the elements in a repeater gets the name
048: * <code>item</code> and a attribute <code>position</code> with the
049: * position of the repeater child, instead of just a number (which is
050: * not allowed as element name). Childs of a
051: * <code>MultiValueField</code> are also embeded within a
052: * <code>item</code> element. If the <code>Form</code> widget does
053: * not have an id it get the name <code>uknown</code>.</p>
054: *
055: * <p>An <code>AggregateField</code> can both be interpreted as one value
056: * and as several widgets. This ambiguity is resolved by chosing to emit
057: * the single value rather than the fields as XML. For population of the
058: * form both forms are however allowed.</p>
059: *
060: * @version $Id: XMLAdapter.java 451957 2006-10-02 10:33:13Z vgritsenko $
061: */
062: public class XMLAdapter extends AbstractXMLConsumer implements
063: XMLizable {
064:
065: /** Name of element in list. */
066: private final static String ITEM = "item";
067: /** Name of unkown element. */
068: private final static String UNKNOWN = "unknown";
069: /** Name of position attribute in list. */
070: private final static String POSITION = "position";
071: /** The namespace prefix of this component. */
072: private final static String PREFIX = "";
073: /** The namespace URI of this component. */
074: private final static String URI = "";
075:
076: /** The <code>ContentHandler</code> receiving SAX events. */
077: private ContentHandler contentHandler;
078: /** The <code>Widget</code> to read and write XML to. */
079: private Widget widget;
080: /** The <code>Widget</code> that we are currently writing to. */
081: private Widget currentWidget;
082: /** The <code>Locale</code> that decides how to convert widget values to strings */
083: private Locale locale;
084: /** Is a <code>MultiValueField</code> handled? */
085: private boolean isMultiValueItem;
086: /** The buffer used to receive character events */
087: private StringBuffer textBuffer;
088:
089: /**
090: * Wrap a <code>Form</code> with an <code>XMLAdapter</code>
091: */
092: public XMLAdapter(Widget widget) {
093: this .widget = widget;
094: this .locale = Locale.US;
095: }
096:
097: /**
098: * Set the locale used for conversion between XML data and Java objects
099: */
100: public void setLocale(Locale locale) {
101: this .locale = locale;
102: }
103:
104: /**
105: * Get the locale used for conversion between XML data and Java objects
106: */
107: public Locale getLocale() {
108: return this .locale;
109: }
110:
111: /* ================ SAX -> Widget ================ */
112:
113: /*
114: * The current state during handling of input events is described
115: * by <code>currentWidget</code> that points to the widget that is
116: * beeing populated. The state that the population has not began
117: * yet or that it is finished is encoded by setting
118: * <code>currentWidget</code> to <code>null</code> and the state of
119: * being within a <code>item</code> within a
120: * <code>MultiValueField</code> is encoded by setting the variable
121: * <code>isMultiValueItem</code> to true.
122: */
123:
124: /**
125: * Receive notification of the beginning of an element.
126: *
127: * @param uri The Namespace URI, or the empty string if the element has no
128: * Namespace URI or if Namespace
129: * processing is not being performed.
130: * @param loc The local name (without prefix), or the empty string if
131: * Namespace processing is not being performed.
132: * @param raw The raw XML 1.0 name (with prefix), or the empty string if
133: * raw names are not available.
134: * @param a The attributes attached to the element. If there are no
135: * attributes, it shall be an empty Attributes object.
136: */
137: public void startElement(String uri, String loc, String raw,
138: Attributes a) throws SAXException {
139: handleText();
140:
141: if (this .currentWidget == null) {
142: // The name of the root element is ignored
143: this .currentWidget = this .widget;
144:
145: } else if (this .currentWidget instanceof ContainerWidget) {
146: Widget child = ((ContainerWidget) this .currentWidget)
147: .getChild(loc);
148: if (child == null) {
149: throw new SAXException("There is no widget with id: "
150: + loc + " as child to: "
151: + this .currentWidget.getId());
152: }
153: this .currentWidget = child;
154:
155: } else if (this .currentWidget instanceof Repeater) {
156: // In a repeater the XML elements are added in the order
157: // they are recieved, the position attribute is not used
158: if (!ITEM.equals(loc)) {
159: throw new SAXException(
160: "The element: "
161: + loc
162: + " is not allowed as a direct child of a Repeater");
163: }
164: Repeater repeater = (Repeater) currentWidget;
165: this .currentWidget = repeater.addRow();
166:
167: } else if (this .currentWidget instanceof MultiValueField) {
168: this .isMultiValueItem = true;
169: if (!ITEM.equals(loc)) {
170: throw new SAXException(
171: "The element: "
172: + loc
173: + " is not allowed as a direct child of a MultiValueField");
174: }
175: }
176: }
177:
178: /**
179: * Receive notification of the end of an element.
180: *
181: * @param uri The Namespace URI, or the empty string if the element has no
182: * Namespace URI or if Namespace
183: * processing is not being performed.
184: * @param loc The local name (without prefix), or the empty string if
185: * Namespace processing is not being performed.
186: * @param raw The raw XML 1.0 name (with prefix), or the empty string if
187: * raw names are not available.
188: */
189: public void endElement(String uri, String loc, String raw)
190: throws SAXException {
191: handleText();
192: if (this .currentWidget == null)
193: throw new SAXException("Wrong state");
194:
195: String id = this .currentWidget.getId();
196:
197: if (this .currentWidget instanceof Form) {
198: this .currentWidget = null;
199: return;
200: } else if (this .currentWidget instanceof AggregateField) {
201: ((AggregateField) this .currentWidget).combineFields();
202: } else if (this .currentWidget instanceof Repeater.RepeaterRow) {
203: id = ITEM;
204: } else if (this .currentWidget instanceof MultiValueField
205: && loc.equals(ITEM)) {
206: this .isMultiValueItem = false;
207: return;
208: }
209:
210: if (loc.equals(id))
211: this .currentWidget = this .currentWidget.getParent();
212: else
213: throw new SAXException("Unexpected element, was: " + loc
214: + " expected: " + id);
215: }
216:
217: /**
218: * Receive notification of character data.
219: *
220: * @param ch The characters from the XML document.
221: * @param start The start position in the array.
222: * @param len The number of characters to read from the array.
223: */
224: public void characters(char ch[], int start, int len)
225: throws SAXException {
226: // Buffer text, as a single text node can be sent in several chunks.
227: if (this .textBuffer == null) {
228: this .textBuffer = new StringBuffer();
229: }
230: this .textBuffer.append(ch, start, len);
231: }
232:
233: /**
234: * Handle text nodes, if any. Called on every potential text node boundary,
235: * i.e. start and end element events.
236: *
237: * @throws SAXException
238: */
239: private void handleText() throws SAXException {
240: if (this .textBuffer == null)
241: return;
242:
243: String input = this .textBuffer.toString().trim();
244: this .textBuffer = null; // clear buffer
245: if (input.length() == 0)
246: return;
247:
248: if (this .currentWidget instanceof MultiValueField
249: && isMultiValueItem) {
250: MultiValueField field = (MultiValueField) this .currentWidget;
251: Datatype type = field.getDatatype();
252: ConversionResult conv = type.convertFromString(input,
253: this .locale);
254: if (!conv.isSuccessful()) {
255: throw new SAXException("Could not convert: " + input
256: + " to " + type.getTypeClass());
257: }
258: Object[] values = (Object[]) field.getValue();
259: int valLen = values == null ? 0 : values.length;
260: Object[] newValues = new Object[valLen + 1];
261: for (int i = 0; i < valLen; i++) {
262: newValues[i] = values[i];
263: }
264: newValues[valLen] = conv.getResult();
265: field.setValues(newValues);
266: } else if (this .currentWidget instanceof DataWidget) {
267: DataWidget data = (DataWidget) this .currentWidget;
268: Datatype type = data.getDatatype();
269: ConversionResult conv = type.convertFromString(input,
270: this .locale);
271: if (!conv.isSuccessful()) {
272: throw new SAXException("Could not convert: " + input
273: + " to " + type.getTypeClass());
274: }
275: data.setValue(conv.getResult());
276: } else if (this .currentWidget instanceof BooleanField) {
277: // FIXME: BooleanField should implement DataWidget, which
278: // would make this case unnecessary
279: if ("true".equals(input))
280: this .currentWidget.setValue(Boolean.TRUE);
281: else if ("false".equals(input))
282: this .currentWidget.setValue(Boolean.FALSE);
283: else
284: throw new SAXException("Unkown boolean: " + input);
285: } else {
286: throw new SAXException("Unknown widget type: "
287: + this .currentWidget);
288: }
289: }
290:
291: /* ================ Widget -> SAX ================ */
292:
293: /*
294: * Just recurses in deep first order over the widget hierarchy and
295: * emits XML
296: */
297:
298: /**
299: * Generates SAX events representing the object's state.
300: */
301: public void toSAX(ContentHandler handler) throws SAXException {
302: this .contentHandler = handler;
303:
304: this .contentHandler.startDocument();
305: this .contentHandler.startPrefixMapping(PREFIX, URI);
306:
307: generateSAX(this .widget);
308:
309: this .contentHandler.endPrefixMapping(PREFIX);
310: this .contentHandler.endDocument();
311: }
312:
313: /**
314: * Generate XML data.
315: */
316: private void generateSAX(Widget widget) throws SAXException {
317: generateSAX(widget, null);
318: }
319:
320: private void generateSAX(Widget widget, String id)
321: throws SAXException {
322:
323: // no XML output for actions
324: if (widget instanceof Action)
325: return;
326:
327: if (id == null)
328: id = widget.getId().length() == 0 ? UNKNOWN : widget
329: .getId();
330:
331: final AttributesImpl attr = new AttributesImpl();
332: if (widget instanceof Repeater.RepeaterRow)
333: attribute(attr, POSITION, widget.getId());
334:
335: start(id, attr);
336: // Placing the handling DataWidget before ContainerWidget
337: // means that an AggregateField is handled like a DataWidget
338: if (widget instanceof MultiValueField) {
339: Datatype datatype = ((MultiValueField) widget)
340: .getDatatype();
341: Object[] values = (Object[]) widget.getValue();
342: if (values != null)
343: for (int i = 0; i < values.length; i++) {
344: start(ITEM, attr);
345: data(datatype.convertToString(values[i],
346: this .locale));
347: end(ITEM);
348: }
349: } else if (widget instanceof DataWidget) {
350: Datatype datatype = ((DataWidget) widget).getDatatype();
351: if (widget.getValue() != null)
352: data(datatype.convertToString(widget.getValue(),
353: this .locale));
354: } else if (widget instanceof BooleanField) {
355: // FIXME: BooleanField should implement DataWidget, which
356: // would make this case unnecessary
357: if (widget.getValue() != null) {
358: data(widget.getValue().toString());
359: }
360: } else if (widget instanceof ContainerWidget) {
361: Iterator children = ((ContainerWidget) widget)
362: .getChildren();
363: while (children.hasNext())
364: generateSAX((Widget) children.next());
365: } else if (widget instanceof Repeater) {
366: Repeater repeater = (Repeater) widget;
367: for (int i = 0; i < repeater.getSize(); i++)
368: generateSAX(repeater.getRow(i), ITEM);
369: }
370: end(id);
371: }
372:
373: private void attribute(AttributesImpl attr, String name,
374: String value) {
375: attr.addAttribute("", name, name, "CDATA", value);
376: }
377:
378: private void start(String name, AttributesImpl attr)
379: throws SAXException {
380: String qName = PREFIX == "" ? name : PREFIX + ":" + name;
381: this .contentHandler.startElement(URI, name, qName, attr);
382: attr.clear();
383: }
384:
385: private void end(String name) throws SAXException {
386: String qName = PREFIX == "" ? name : PREFIX + ":" + name;
387: this .contentHandler.endElement(URI, name, qName);
388: }
389:
390: private void data(String data) throws SAXException {
391: this .contentHandler.characters(data.toCharArray(), 0, data
392: .length());
393: }
394: }
|