001: /**
002: * Copyright (C) 2004-2007 Jive Software. All rights reserved.
003: *
004: * This software is published under the terms of the GNU Public License (GPL),
005: * a copy of which is included in this distribution.
006: */package org.xmpp.forms;
007:
008: import org.dom4j.Element;
009: import org.dom4j.QName;
010: import org.jivesoftware.util.FastDateFormat;
011: import org.jivesoftware.util.JiveConstants;
012: import org.xmpp.packet.PacketExtension;
013:
014: import java.text.ParseException;
015: import java.text.SimpleDateFormat;
016: import java.util.*;
017:
018: /**
019: * Represents a form that could be use for gathering data as well as for reporting data
020: * returned from a search.
021: * <p/>
022: * The form could be of the following types:
023: * <ul>
024: * <li>form -> Indicates a form to fill out.</li>
025: * <li>submit -> The form is filled out, and this is the data that is being returned from
026: * the form.</li>
027: * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
028: * <li>result -> Data results being returned from a search, or some other query.</li>
029: * </ul>
030: * <p/>
031: * In case the form represents a search, the report will be structured in columns and rows. Use
032: * {@link #addReportedField(String,String,FormField.Type)} to set the columns of the report whilst
033: * the report's rows can be configured using {@link #addItemFields(Map)}.
034: *
035: * @author Gaston Dombiak
036: */
037: public class DataForm extends PacketExtension {
038:
039: private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat(
040: JiveConstants.XMPP_DELAY_DATETIME_FORMAT);
041: private static final FastDateFormat FAST_UTC_FORMAT = FastDateFormat
042: .getInstance(JiveConstants.XMPP_DELAY_DATETIME_FORMAT,
043: TimeZone.getTimeZone("UTC"));
044:
045: /**
046: * Element name of the packet extension.
047: */
048: public static final String ELEMENT_NAME = "x";
049:
050: /**
051: * Namespace of the packet extension.
052: */
053: public static final String NAMESPACE = "jabber:x:data";
054:
055: static {
056: UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
057: // Register that DataForms uses the jabber:x:data namespace
058: registeredExtensions.put(QName.get(ELEMENT_NAME, NAMESPACE),
059: DataForm.class);
060: }
061:
062: /**
063: * Returns the Date obtained by parsing the specified date representation. The date
064: * representation is expected to be in the UTC GMT+0 format.
065: *
066: * @param date date representation in the UTC GMT+0 format.
067: * @return the Date obtained by parsing the specified date representation.
068: * @throws ParseException if an error occurs while parsing the date representation.
069: */
070: public static Date parseDate(String date) throws ParseException {
071: synchronized (UTC_FORMAT) {
072: return UTC_FORMAT.parse(date);
073: }
074: }
075:
076: public static boolean parseBoolean(String booleanString)
077: throws ParseException {
078: return "1".equals(booleanString)
079: || "true".equals(booleanString);
080: }
081:
082: /**
083: * Returns the String representation of an Object to be used as a field value.
084: *
085: * @param object the object to encode.
086: * @return the String representation of an Object to be used as a field value.
087: */
088: static String encode(Object object) {
089: if (object instanceof String) {
090: return object.toString();
091: } else if (object instanceof Boolean) {
092: return Boolean.TRUE.equals(object) ? "1" : "0";
093: } else if (object instanceof Date) {
094: return FAST_UTC_FORMAT.format((Date) object);
095: }
096: return object.toString();
097: }
098:
099: public DataForm(Type type) {
100: super (ELEMENT_NAME, NAMESPACE);
101: // Set the type of the data form
102: element.addAttribute("type", type.toString());
103: }
104:
105: public DataForm(Element element) {
106: super (element);
107: }
108:
109: /**
110: * Returns the type of this data form.
111: *
112: * @return the data form type.
113: * @see org.xmpp.forms.DataForm.Type
114: */
115: public DataForm.Type getType() {
116: String type = element.attributeValue("type");
117: if (type != null) {
118: return DataForm.Type.valueOf(type);
119: } else {
120: return null;
121: }
122: }
123:
124: /**
125: * Sets the description of the data. It is similar to the title on a web page or an X window.
126: * You can put a <title/> on either a form to fill out, or a set of data results.
127: *
128: * @param title description of the data.
129: */
130: public void setTitle(String title) {
131: // Remove an existing title element.
132: if (element.element("title") != null) {
133: element.remove(element.element("title"));
134: }
135: element.addElement("title").setText(title);
136: }
137:
138: /**
139: * Returns the description of the data form. It is similar to the title on a web page or an X
140: * window. You can put a <title/> on either a form to fill out, or a set of data results.
141: *
142: * @return description of the data.
143: */
144: public String getTitle() {
145: return element.elementTextTrim("title");
146: }
147:
148: /**
149: * Returns an unmodifiable list of instructions that explain how to fill out the form and
150: * what the form is about. The dataform could include multiple instructions since each
151: * instruction could not contain newlines characters.
152: *
153: * @return an unmodifiable list of instructions that explain how to fill out the form.
154: */
155: public List<String> getInstructions() {
156: List<String> answer = new ArrayList<String>();
157: for (Iterator it = element.elementIterator("instructions"); it
158: .hasNext();) {
159: answer.add(((Element) it.next()).getTextTrim());
160: }
161: return Collections.unmodifiableList(answer);
162: }
163:
164: /**
165: * Adds a new instruction to the list of instructions that explain how to fill out the form
166: * and what the form is about. The dataform could include multiple instructions since each
167: * instruction could not contain newlines characters.
168: *
169: * @param instruction the new instruction that explain how to fill out the form.
170: */
171: public void addInstruction(String instruction) {
172: element.addElement("instructions").setText(instruction);
173: }
174:
175: /**
176: * Clears all the stored instructions in this packet extension.
177: */
178: public void clearInstructions() {
179: for (Iterator it = element.elementIterator("instructions"); it
180: .hasNext();) {
181: it.next();
182: it.remove();
183: }
184: }
185:
186: /**
187: * Adds a new field as part of the form.
188: *
189: * @return the newly created field.
190: */
191: public FormField addField() {
192: return new FormField(element.addElement("field"));
193: }
194:
195: /**
196: * Returns the fields that are part of the form.
197: *
198: * @return fields that are part of the form.
199: */
200: public List<FormField> getFields() {
201: List<FormField> answer = new ArrayList<FormField>();
202: for (Iterator it = element.elementIterator("field"); it
203: .hasNext();) {
204: answer.add(new FormField((Element) it.next()));
205: }
206: return answer;
207: }
208:
209: /**
210: * Returns the field whose variable matches the specified variable.
211: *
212: * @param variable the variable name of the field to search.
213: * @return the field whose variable matches the specified variable
214: */
215: public FormField getField(String variable) {
216: for (Iterator it = element.elementIterator("field"); it
217: .hasNext();) {
218: FormField formField = new FormField((Element) it.next());
219: if (variable.equals(formField.getVariable())) {
220: return formField;
221: }
222: }
223: return null;
224: }
225:
226: /**
227: * Removes the field whose variable matches the specified variable.
228: *
229: * @param variable the variable name of the field to remove.
230: * @return true if the field was removed.
231: */
232: public boolean removeField(String variable) {
233: for (Iterator it = element.elementIterator("field"); it
234: .hasNext();) {
235: Element field = (Element) it.next();
236: String fieldVariable = field.attributeValue("var");
237: if (variable.equals(fieldVariable)) {
238: return element.remove(field);
239: }
240: }
241: return false;
242: }
243:
244: /**
245: * Adds a field to the list of fields that will be returned from a search. Each field represents
246: * a column in the report. The order of the columns in the report will honor the sequence in
247: * which they were added.
248: *
249: * @param variable variable name of the new column. This value will be used in
250: * {@link #addItemFields} when adding reported items.
251: * @param label label that corresponds to the new column. Optional parameter.
252: * @param type indicates the type of field of the new column. Optional parameter.
253: */
254: public void addReportedField(String variable, String label,
255: FormField.Type type) {
256: Element reported = element.element("reported");
257: synchronized (element) {
258: if (reported == null) {
259: reported = element.element("reported");
260: if (reported == null) {
261: reported = element.addElement("reported");
262: }
263: }
264: }
265: FormField newField = new FormField(reported.addElement("field"));
266: newField.setVariable(variable);
267: newField.setType(type);
268: newField.setLabel(label);
269: }
270:
271: /**
272: * Adds a new row of items of reported data. For each entry in the <tt>fields</tt> parameter
273: * a <tt>field</tt> element will be added to the <item> element. The variable of the new
274: * <tt>field</tt> will be the key of the entry. The new <tt>field</tt> will have several values
275: * if the entry's value is a {@link Collection}. Since the value is of type {@link Object} it
276: * is possible to include any type of object as a value. The actual value to include in the
277: * data form is the result of the {@link #encode(Object)} method.
278: *
279: * @param fields list of <variable,value> to be added as a new item.
280: */
281: public void addItemFields(Map<String, Object> fields) {
282: Element item = element.addElement("item");
283: // Add a field element to the item element for each row in fields
284: for (String var : fields.keySet()) {
285: Element field = item.addElement("field");
286: field.addAttribute("var", var);
287: Object value = fields.get(var);
288: if (value instanceof Collection) {
289: // Add a value element for each entry in the collection
290: for (Iterator it = ((Collection) value).iterator(); it
291: .hasNext();) {
292: field.addElement("value")
293: .setText(encode(it.next()));
294: }
295: } else {
296: field.addElement("value").setText(encode(value));
297: }
298: }
299: }
300:
301: public DataForm createCopy() {
302: return new DataForm(this .getElement().createCopy());
303: }
304:
305: /**
306: * Type-safe enumeration to represent the type of the Data forms.
307: */
308: public enum Type {
309: /**
310: * The forms-processing entity is asking the forms-submitting entity to complete a form.
311: */
312: form,
313:
314: /**
315: * The forms-submitting entity is submitting data to the forms-processing entity.
316: */
317: submit,
318:
319: /**
320: * The forms-submitting entity has cancelled submission of data to the forms-processing
321: * entity.
322: */
323: cancel,
324:
325: /**
326: * The forms-processing entity is returning data (e.g., search results) to the
327: * forms-submitting entity, or the data is a generic data set.
328: */
329: result;
330: }
331: }
|