View Javadoc

1   /*
2    * Copyright (c) 2001 - 2005 ivata limited.
3    * All rights reserved.
4    * -----------------------------------------------------------------------------
5    * ivata masks may be redistributed under the GNU General Public
6    * License as published by the Free Software Foundation;
7    * version 2 of the License.
8    *
9    * These programs are free software; you can redistribute them and/or
10   * modify them under the terms of the GNU General Public License
11   * as published by the Free Software Foundation; version 2 of the License.
12   *
13   * These programs are distributed in the hope that they will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16   *
17   * See the GNU General Public License in the file LICENSE.txt for more
18   * details.
19   *
20   * If you would like a copy of the GNU General Public License write to
21   *
22   * Free Software Foundation, Inc.
23   * 59 Temple Place - Suite 330
24   * Boston, MA 02111-1307, USA.
25   *
26   *
27   * To arrange commercial support and licensing, contact ivata at
28   *                  http://www.ivata.com/contact.jsp
29   * -----------------------------------------------------------------------------
30   * $Log: ValueObjectFieldWriter.java,v $
31   * Revision 1.8  2005/04/09 18:04:18  colinmacleod
32   * Changed copyright text to GPL v2 explicitly.
33   *
34   * Revision 1.7  2005/01/19 12:50:34  colinmacleod
35   * Added attribute delegate methods.
36   *
37   * Revision 1.6  2005/01/11 10:12:28  colinmacleod
38   * Added workaround for when valuesParam is null.
39   *
40   * Revision 1.5  2005/01/07 08:08:23  colinmacleod
41   * Moved up a version number.
42   * Changed copyright notices to 2005.
43   * Updated the documentation:
44   *   - started working on multiproject:site docu.
45   *   - changed the logo.
46   * Added checkstyle and fixed LOADS of style issues.
47   * Added separate thirdparty subproject.
48   * Added struts (in web), util and webgui (in webtheme) from ivata op.
49   *
50   * Revision 1.4  2004/12/30 20:20:42  colinmacleod
51   * Set style class if mandatory.
52   *
53   * Revision 1.3  2004/12/29 15:30:10  colinmacleod
54   * Added asserts to check parameters are not null.
55   *
56   * Revision 1.2  2004/12/23 21:28:31  colinmacleod
57   * Modifications to add ivata op compatibility.
58   *
59   * Revision 1.1  2004/11/11 13:41:08  colinmacleod
60   * First version in CVS. Added to display value object contents.
61   *
62   * Revision 1.1.1.1  2004/05/16 20:40:32  colinmacleod
63   * Ready for 0.1 release
64   * -----------------------------------------------------------------------------
65   */
66  package com.ivata.mask.web.field.valueobject;
67  import com.ivata.mask.Mask;
68  import com.ivata.mask.field.Field;
69  import com.ivata.mask.util.StringHandling;
70  import com.ivata.mask.valueobject.ValueObject;
71  import com.ivata.mask.web.field.AttributesWriter;
72  import com.ivata.mask.web.field.FieldWriter;
73  import com.ivata.mask.web.format.HTMLFormatter;
74  import org.apache.commons.beanutils.PropertyUtils;
75  import org.apache.struts.taglib.TagUtils;
76  import org.dom4j.Document;
77  import org.dom4j.DocumentFactory;
78  import org.dom4j.Element;
79  import java.lang.reflect.InvocationTargetException;
80  import java.net.MalformedURLException;
81  import java.util.Collection;
82  import java.util.HashMap;
83  import java.util.Iterator;
84  import java.util.List;
85  import java.util.Map;
86  import java.util.Vector;
87  import javax.servlet.jsp.PageContext;
88  /***
89   * <p>
90   * This writer is used to display links to other value objects.
91   * </p>
92   *
93   * @since ivata masks 0.2 (2004-05-14)
94   * @author Colin MacLeod
95   * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
96   * @version $Revision: 1.8 $
97   */
98  public class ValueObjectFieldWriter implements FieldWriter {
99      /***
100      * <p>
101      * This is the style class used for combo (single choice) lists.
102      * </p>
103      */
104     public static final String CSS_COMBO = "combo";
105     /***
106      * <p>
107      * This is the style class used for multiple choice lists.
108      * </p>
109      */
110     public static final String CSS_LIST = "list";
111     /***
112      * <p>
113      * This is the value which is output as a default. TODO: i18n
114      * </p>
115      */
116     public static final String DEFAULT_DISPLAY_VALUE = "[None]";
117     /***
118      * <p>
119      * This is the value which is output as a default. TODO: i18n
120      * </p>
121      */
122     public static final String DEFAULT_VALUE = "";
123     /***
124      * <p>
125      * This is the value which is output as a new entry. TODO: i18n
126      * </p>
127      */
128     public static final String NEW_DISPLAY_VALUE = "[New]";
129     /***
130      * <p>
131      * Local page of the action to which we'll link, for a read-only field.
132      * </p>
133      */
134     private String actionPage;
135     /***
136      * Refer to {@link #getAllValueObjects}.
137      */
138     private Collection allValueObjects;
139     /***
140      * <p>
141      * Stores all of the field's attributes and values, then writes them out
142      * later.
143      * </p>
144      */
145     private AttributesWriter attributesWriter;
146     /***
147      * <p>
148      * Field to be displayed.
149      * </p>
150      */
151     private Field field;
152     /***
153      * <p>
154      * Used to format the returned text.
155      * </p>
156      */
157     private HTMLFormatter formatter;
158     /***
159      * <p>
160      * Stores all of the hidden field's attributes and values, then writes them
161      * out later. This is used for a sublist.
162      * </p>
163      */
164     private AttributesWriter hiddenAttributesWriter;
165     /***
166      * <p>
167      * Construct a field writer.
168      * </p>
169      *
170      * @param fieldParam
171      *            defines the field to be displayed.
172      * @param actionPageParam
173      *            Refer to {@link DefaultFieldWriter#getActionPage}.
174      * @param allValueObjectsParam
175      *            Refer to {@link #getAllValueObjects}.
176      * @param formatterParam
177      *            Refer to {@link #getFormatter}.
178      * @param listHeightParam
179      *            set as the size attribute in the field.
180      * @param multipleParam
181      *            if <code>true</code>, then the user can make more than one
182      *            selection.
183      */
184     public ValueObjectFieldWriter(final Field fieldParam,
185             final String actionPageParam,
186             final Collection allValueObjectsParam,
187             final HTMLFormatter formatterParam, final int listHeightParam,
188             final boolean multipleParam) {
189         super();
190         this.field = fieldParam;
191         this.actionPage = actionPageParam;
192         this.allValueObjects = allValueObjectsParam;
193         this.formatter = formatterParam;
194         boolean multiple = multipleParam;
195         // we don't want to actually submit the values in a sublist
196         String propertyNameSuffix;
197         if (isSublist()) {
198             propertyNameSuffix = "_sublist";
199             // sublists can be multi-choice
200             multiple = false;
201         } else {
202             propertyNameSuffix = "";
203         }
204         attributesWriter = new AttributesWriter(fieldParam, propertyNameSuffix);
205         attributesWriter
206                 .setAttribute("size", Integer.toString(listHeightParam));
207         if (fieldParam.isMandatory()) {
208             attributesWriter.appendAttribute("class", "mandatory");
209         }
210         if (multiple) {
211             attributesWriter.setAttribute("multiple", "multiple");
212             // if the field type wasn't set explicitly, set the class here
213             if (StringHandling.isNullOrEmpty(fieldParam.getType())) {
214                 attributesWriter.appendAttribute("class", CSS_LIST);
215             }
216         } else {
217             attributesWriter.remove("multiple");
218             // if the field type wasn't set explicitly, set the class here
219             if (StringHandling.isNullOrEmpty(fieldParam.getType())) {
220                 attributesWriter.appendAttribute("class", CSS_COMBO);
221             }
222         }
223     }
224     /***
225      * All possible value objects to display in a list - for a choice. The user
226      * will be given the chance to choose from only these values.
227      *
228      * @return Returns the all value objects as a <code>Collection</code> of
229      *         <code>ValueObject</code> instances.
230      */
231     protected final Collection getAllValueObjects() {
232         return allValueObjects;
233     }
234     /***
235      * <p>
236      * Access the attributes writer, which is responsible for converting the
237      * field attributes into text.
238      * </p>
239      *
240      * @return attributes writer.
241      */
242     protected final AttributesWriter getAttributesWriter() {
243         return attributesWriter;
244     }
245     /***
246      * <p>
247      * Access the field to be displayed.
248      * </p>
249      *
250      * @return field to be displayed.
251      */
252     protected final Field getField() {
253         return field;
254     }
255     /***
256      * Used to format the displayed, usually ensuring line breaks are converted
257      * into HTML.
258      *
259      * @return Returns the formatter.
260      */
261     protected final HTMLFormatter getFormatter() {
262         return formatter;
263     }
264     /***
265      * <p>
266      * Find out whether or not this field represents a sublist.
267      * </p>
268      *
269      * @return <code>true</code> if this is a sublist.
270      */
271     private boolean isSublist() {
272         return "sublist".equals(field.getType());
273     }
274     /***
275      * Refer to {@link FieldWriter#clearAttribute}.
276      *
277      * @param name Refer to {@link FieldWriter#clearAttribute}.
278      */
279     public void removeAttribute(final String name) {
280         attributesWriter.remove(name);
281     }
282     /***
283      * Refer to {@link FieldWriter#setAttribute}.
284      *
285      * @param name Refer to {@link FieldWriter#setAttribute}.
286      * @param value Refer to {@link FieldWriter#setAttribute}.
287      */
288     public void setAttribute(final String name, final String value) {
289         attributesWriter.setAttribute(name, value);
290     }
291     /***
292      * Refer to {@link com.ivata.mask.web.field.FieldWriter#write}.
293      *
294      * @param pageContextParam
295      *            Refer to {@link com.ivata.mask.web.field.FieldWriter#write}.
296      * @param valueObjectParam
297      *            Refer to {@link com.ivata.mask.web.field.FieldWriter#write}.
298      * @param displayOnlyParam
299      *            Refer to {@link com.ivata.mask.web.field.FieldWriter#write}.
300      * @return Refer to {@link com.ivata.mask.web.field.FieldWriter#write}.
301      */
302     public final String write(final PageContext pageContextParam,
303             final ValueObject valueObjectParam,
304             final boolean displayOnlyParam) {
305         assert (pageContextParam != null);
306         assert (valueObjectParam != null);
307         Collection values;
308         Object object;
309         try {
310             object = PropertyUtils.getProperty(valueObjectParam, field
311                     .getPath());
312             // this lets us handle both collections and individual value objects
313             // in the same way
314             if (object != null) {
315                 if (object instanceof ValueObject) {
316                     values = new Vector();
317                     values.add(object);
318                 } else {
319                     // it must be a collection (will throw class cast exception
320                     // if you tried to assign something else)
321                     values = (Collection) object;
322                 }
323             } else {
324                 values = null;
325             }
326         } catch (IllegalAccessException e) {
327             throw new RuntimeException("ERROR (" + e.getClass().getName()
328                     + ") in ValueObjectFieldWriter: " + e.getMessage());
329         } catch (InvocationTargetException e) {
330             throw new RuntimeException("ERROR (" + e.getClass().getName()
331                     + ") in ValueObjectFieldWriter: " + e.getMessage());
332         } catch (NoSuchMethodException e) {
333             // if there is no matching method, just set all values to null
334             values = null;
335         }
336         if (displayOnlyParam) {
337             return writeDisplayOnly(pageContextParam, values);
338         } else {
339             return writeChoiceField(pageContextParam, values);
340         }
341     }
342     /***
343      * Display a field (which is not read-only).
344      *
345      * @param pageContextParam
346      *            Refer to {@link com.ivata.mask.web.field.FieldWriter#write}.
347      * @param valuesParam
348      *            contains all the possible values as strings.
349      * @return string representing the combo box.
350      */
351     public final String writeChoiceField(final PageContext pageContextParam,
352             final Collection valuesParam) {
353         List values = new Vector();
354         if (valuesParam != null) {
355             values.addAll(valuesParam);
356         }
357         // if there is nothing to display, display, well, nothing...
358         if (allValueObjects.size() == 0) {
359             return DEFAULT_DISPLAY_VALUE;
360         }
361         // if no current values are specified, match the default value
362         if (values == null) {
363             values = new Vector();
364             values.add(DEFAULT_VALUE);
365         }
366         // clear the value attribute if there is no value
367         String stringValue;
368         StringBuffer buffer = new StringBuffer();
369         // if this is a sublist, we actually store the entries to submit in XML
370         // in a hidden field
371         Iterator allValueIterator;
372         if (isSublist()) {
373             writeHiddenContents(buffer, field, values);
374             // sublists call JavaScript to do their funky stuff
375             String onChangeSublist = "onChangeSublist(\""
376                     + hiddenAttributesWriter.getAttribute("id")
377                     + "\");return true";
378             attributesWriter.setAttribute("onchange", onChangeSublist);
379             // for a sublist, we only show the values in this list - not all
380             // possible values
381             allValueIterator = values.iterator();
382         } else {
383             // for all other choices, we show all possible values so you can
384             // choose one or more
385             allValueIterator = allValueObjects.iterator();
386         }
387         buffer.append("<select");
388         buffer.append(attributesWriter.toString());
389         buffer.append(">");
390         // if this is not a mandatory field, show the default value
391         if (!isSublist() && !field.isMandatory()) {
392             buffer.append("\n<option value='");
393             buffer.append(DEFAULT_VALUE);
394             buffer.append("'>");
395             buffer.append(DEFAULT_DISPLAY_VALUE);
396             buffer.append("</option>");
397         }
398         while (allValueIterator.hasNext()) {
399             ValueObject thisObject = (ValueObject) allValueIterator.next();
400             buffer.append("\n<option value='");
401             buffer.append(thisObject.getIdString());
402             // check all chosen values against this value
403             Iterator valueIterator = values.iterator();
404             while (valueIterator.hasNext()) {
405                 Object value = valueIterator.next();
406                 String idString;
407                 if (value instanceof String) {
408                     idString = (String) value;
409                 } else {
410                     idString = ((ValueObject) value).getIdString();
411                 }
412                 // only put out selections if this is not a sublist field
413                 if (idString.equals(thisObject.getIdString()) && !isSublist()) {
414                     buffer.append("' selected='selected");
415                     break;
416                 }
417             }
418             String displayValue;
419             // sublist displays the field values themselves - this is easier to
420             // replicate in the JavaScript (than the getDisplayValue method)
421             if (isSublist()) {
422                 Mask mask = field.getValueObjectMask();
423                 Iterator fieldsIterator = mask.getFields().iterator();
424                 StringBuffer displayValueBuffer = new StringBuffer();
425                 while (fieldsIterator.hasNext()) {
426                     if (displayValueBuffer.length() > 0) {
427                         displayValueBuffer.append(" ");
428                     }
429                     Field subField = (Field) fieldsIterator.next();
430                     String subDisplayValue;
431                     Object subObject;
432                     try {
433                         subObject = PropertyUtils.getProperty(thisObject,
434                                 subField.getName());
435                     } catch (IllegalAccessException e) {
436                         throw new RuntimeException(e);
437                     } catch (InvocationTargetException e) {
438                         throw new RuntimeException(e);
439                     } catch (NoSuchMethodException e) {
440                         throw new RuntimeException(e);
441                     }
442                     if (subObject == null) {
443                         subDisplayValue = DEFAULT_DISPLAY_VALUE;
444                     } else if (subObject instanceof ValueObject) {
445                         subDisplayValue = ((ValueObject) subObject)
446                                 .getDisplayValue();
447                     } else {
448                         subDisplayValue = subObject.toString();
449                     }
450                     displayValueBuffer.append(subDisplayValue);
451                 }
452                 displayValue = displayValueBuffer.toString();
453             } else {
454                 displayValue = thisObject.getDisplayValue();
455             }
456             buffer.append("'>");
457             buffer.append(formatter.format(displayValue));
458             buffer.append("</option>");
459         }
460         // if this is a sublist append a 'new' option
461         if (isSublist()) {
462             buffer.append("\n<option value='");
463             buffer.append(DEFAULT_VALUE);
464             buffer.append("' selected='selected'>");
465             buffer.append(NEW_DISPLAY_VALUE);
466             buffer.append("</option>");
467         }
468         buffer.append("</select>");
469         stringValue = buffer.toString();
470         return stringValue;
471     }
472     /***
473      * Display the correct field value from the value object - for a read-only
474      * field.
475      *
476      * @param pageContextParam
477      *            Refer to {@link com.ivata.mask.web.field.FieldWriter#write}.
478      * @param valuesParam
479      *            Refer to {@link #writeChoiceField}.
480      * @return Refer to {@link #writeChoiceField}.
481      * @see com.ivata.mask.web.field.FieldWriter#write
482      */
483     public final String writeDisplayOnly(final PageContext pageContextParam,
484             final Collection valuesParam) {
485         // if there are no values, display nothing!
486         if (valuesParam == null) {
487             return "";
488         }
489         Iterator valueIterator = valuesParam.iterator();
490         StringBuffer buffer = new StringBuffer();
491         String type = field.getType();
492         while (valueIterator.hasNext()) {
493             ValueObject valueObject = (ValueObject) valueIterator.next();
494             // clear the value attribute if there is no value
495             String stringValue = valueObject.getDisplayValue();
496             attributesWriter.setAttribute("value", stringValue);
497             // don't link sublists - they are input on the mask for the
498             // containing do directly
499             if (!"sublist".equals(type)) {
500                 buffer.append("<a href='");
501                 try {
502                     Map params = new HashMap();
503                     params.put("baseClass", valueObject.getClass().getName());
504                     params.put("idString", valueObject.getIdString());
505                     params.put("displayOnly", "true");
506                     String uRL = TagUtils.getInstance().computeURL(
507                             pageContextParam, null, null, actionPage, null,
508                             null, params, null, false);
509                     buffer.append(uRL);
510                 } catch (MalformedURLException e) {
511                     throw new RuntimeException("ERROR ("
512                             + e.getClass().getName()
513                             + ") in ValueObjectFieldWriter: " + e.getMessage());
514                 }
515                 buffer.append("'>");
516             }
517             buffer.append(valueObject.getDisplayValue());
518             if (!"sublist".equals(type)) {
519                 buffer.append("</a>");
520             }
521             // if there are more elements after this one, put them on the next
522             // line
523             if (valueIterator.hasNext()) {
524                 buffer.append("<br/>\n");
525             }
526         }
527         return buffer.toString();
528     }
529     /***
530      * <p>
531      * If this is a sublist, we actually store the entries to submit in XML in a
532      * hidden field. This method generates that hidden field into the string
533      * buffer provided.
534      * </p>
535      *
536      * @param bufferParam
537      *            String buffer into which we will write the results.
538      * @param fieldParam
539      *            Field to be written out.
540      * @param valuesParam
541      *            Refer to {@link #writeChoiceField}.
542      */
543     private void writeHiddenContents(final StringBuffer bufferParam,
544             final Field fieldParam, final Collection valuesParam) {
545         Document document = DocumentFactory.getInstance().createDocument();
546         Element rootElement = document.addElement("sublist");
547         Mask mask = fieldParam.getValueObjectMask();
548         // go through all the values and output them
549         Iterator valueIterator = valuesParam.iterator();
550         while (valueIterator.hasNext()) {
551             ValueObject valueObject = (ValueObject) valueIterator.next();
552             Element dOElement = rootElement.addElement("valueObject");
553             dOElement.addAttribute("id", valueObject.getIdString());
554             // now output all fields of this value object
555             Iterator fieldIterator = mask.getFields().iterator();
556             while (fieldIterator.hasNext()) {
557                 Field subField = (Field) fieldIterator.next();
558                 Element fieldElement = dOElement.addElement("field");
559                 fieldElement.addAttribute("id", subField.getName());
560                 Object value;
561                 try {
562                     value = PropertyUtils.getProperty(valueObject, subField
563                             .getName());
564                 } catch (IllegalAccessException e) {
565                     throw new RuntimeException("ERROR ("
566                             + e.getClass().getName()
567                             + ") in ValueObjectFieldWriter: " + e.getMessage(),
568                             e);
569                 } catch (InvocationTargetException e) {
570                     throw new RuntimeException("ERROR ("
571                             + e.getClass().getName()
572                             + ") in ValueObjectFieldWriter: " + e.getMessage(),
573                             e);
574                 } catch (NoSuchMethodException e) {
575                     throw new RuntimeException("ERROR ("
576                             + e.getClass().getName()
577                             + ") in ValueObjectFieldWriter: " + e.getMessage(),
578                             e);
579                 }
580                 if (value != null) {
581                     // if it is a value object, then set the value to the id
582                     if (value instanceof ValueObject) {
583                         fieldElement.setText(((ValueObject) value)
584                                 .getIdString());
585                     } else {
586                         // otherwise use the string value
587                         fieldElement.setText(value.toString());
588                     }
589                 }
590             }
591         }
592         // finally add empty ones with no id - this ensures we always have the
593         // field names
594         Iterator fieldIterator = mask.getFields().iterator();
595         Element dOElement = rootElement.addElement("valueObject");
596         dOElement.addAttribute("schema", "true");
597         while (fieldIterator.hasNext()) {
598             Field subField = (Field) fieldIterator.next();
599             Element fieldElement = dOElement.addElement("field");
600             fieldElement.addAttribute("id", subField.getName());
601             fieldElement.setText("");
602         }
603         // now output all fields of this value object
604         hiddenAttributesWriter = new AttributesWriter(fieldParam);
605         hiddenAttributesWriter.setAttribute("type", "hidden");
606         hiddenAttributesWriter.setAttribute("value", document.asXML());
607         bufferParam.append("<input");
608         bufferParam.append(hiddenAttributesWriter.toString());
609         bufferParam.append("/>");
610         // id field holds the value of the value object id
611         AttributesWriter idAttributesWriter = new AttributesWriter(fieldParam,
612                 "_id");
613         idAttributesWriter.setAttribute("type", "hidden");
614         bufferParam.append("<input");
615         bufferParam.append(idAttributesWriter.toString());
616         bufferParam.append("/>");
617         // index field holds the index of the item within the select
618         AttributesWriter indexAttributesWriter = new AttributesWriter(
619                 fieldParam, "_index");
620         indexAttributesWriter.setAttribute("type", "hidden");
621         bufferParam.append("<input");
622         bufferParam.append(indexAttributesWriter.toString());
623         bufferParam.append("/>");
624     }
625 }
626