001: /*
002: * Copyright (c) 2001 - 2005 ivata limited.
003: * All rights reserved.
004: * -----------------------------------------------------------------------------
005: * ivata masks may be redistributed under the GNU General Public
006: * License as published by the Free Software Foundation;
007: * version 2 of the License.
008: *
009: * These programs are free software; you can redistribute them and/or
010: * modify them under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; version 2 of the License.
012: *
013: * These programs are distributed in the hope that they will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * See the GNU General Public License in the file LICENSE.txt for more
018: * details.
019: *
020: * If you would like a copy of the GNU General Public License write to
021: *
022: * Free Software Foundation, Inc.
023: * 59 Temple Place - Suite 330
024: * Boston, MA 02111-1307, USA.
025: *
026: *
027: * To arrange commercial support and licensing, contact ivata at
028: * http://www.ivata.com/contact.jsp
029: * -----------------------------------------------------------------------------
030: * $Log: DefaultFieldWriterFactory.java,v $
031: * Revision 1.15 2005/10/11 18:54:06 colinmacleod
032: * Fixed some checkstyle and javadoc issues.
033: *
034: * Revision 1.14 2005/10/03 10:17:25 colinmacleod
035: * Fixed some style and javadoc issues.
036: *
037: * Revision 1.13 2005/10/02 14:06:32 colinmacleod
038: * Added/improved log4j logging.
039: *
040: * Revision 1.12 2005/09/29 12:12:30 colinmacleod
041: * Added password field type.
042: *
043: * Revision 1.11 2005/09/16 13:41:18 colinmacleod
044: * Created new convertor class to handle timestamps.
045: *
046: * Revision 1.10 2005/09/14 12:54:31 colinmacleod
047: * Added checking for hidden fields of
048: * numeric or date types.
049: *
050: * Revision 1.9 2005/04/09 18:04:17 colinmacleod
051: * Changed copyright text to GPL v2 explicitly.
052: *
053: * Revision 1.8 2005/01/19 12:51:03 colinmacleod
054: * Changed Id --> Name.
055: *
056: * Revision 1.7 2005/01/10 19:05:12 colinmacleod
057: * Removed reflection and simplified override method
058: * for field writer classes.
059: *
060: * Revision 1.6 2005/01/07 08:08:24 colinmacleod
061: * Moved up a version number.
062: * Changed copyright notices to 2005.
063: * Updated the documentation:
064: * - started working on multiproject:site docu.
065: * - changed the logo.
066: * Added checkstyle and fixed LOADS of style issues.
067: * Added separate thirdparty subproject.
068: * Added struts (in web), util and webgui (in webtheme) from ivata op.
069: *
070: * Revision 1.5 2004/12/30 20:27:55 colinmacleod
071: * Added reflection to let you override the field writer classes used.
072: *
073: * Revision 1.4 2004/12/29 15:30:46 colinmacleod
074: * Added asserts to check parameters are not null.
075: *
076: * Revision 1.3 2004/11/12 15:10:41 colinmacleod
077: * Moved persistence classes from ivata op as a replacement for
078: * ValueObjectLocator.
079: *
080: * Revision 1.2 2004/11/11 13:44:04 colinmacleod
081: * Added HTMLFormatter.
082: *
083: * Revision 1.1.1.1 2004/05/16 20:40:32 colinmacleod
084: * Ready for 0.1 release
085: * -----------------------------------------------------------------------------
086: */
087: package com.ivata.mask.web.field;
088:
089: import org.apache.log4j.Logger;
090:
091: import java.beans.PropertyDescriptor;
092: import java.lang.reflect.InvocationTargetException;
093: import java.util.Collection;
094:
095: import javax.servlet.http.HttpSession;
096:
097: import org.apache.commons.beanutils.PropertyUtils;
098:
099: import com.ivata.mask.field.Field;
100: import com.ivata.mask.field.FieldValueConvertor;
101: import com.ivata.mask.field.date.DateFieldValueConvertor;
102: import com.ivata.mask.field.date.TimestampFieldValueConvertor;
103: import com.ivata.mask.field.number.NumberFieldValueConvertor;
104: import com.ivata.mask.field.valueobject.ValueObjectFieldValueConvertor;
105: import com.ivata.mask.persistence.PersistenceManager;
106: import com.ivata.mask.persistence.PersistenceSession;
107: import com.ivata.mask.util.SystemException;
108: import com.ivata.mask.valueobject.ValueObject;
109: import com.ivata.mask.web.field.hidden.HiddenFieldWriter;
110: import com.ivata.mask.web.field.text.PasswordFieldWriter;
111: import com.ivata.mask.web.field.text.TextAreaFieldWriter;
112: import com.ivata.mask.web.field.text.TextFieldWriter;
113: import com.ivata.mask.web.field.valueobject.ValueObjectFieldWriter;
114: import com.ivata.mask.web.format.HTMLFormatter;
115: import com.ivata.mask.web.format.LineBreakFormat;
116:
117: /**
118: * <p>
119: * Use this utility class to generate an appropriate field writer for a given
120: * mask and field.
121: * </p>
122: *
123: * @since ivata masks 0.1 (2004-05-14)
124: * @author Colin MacLeod
125: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
126: * @version $Revision: 1.15 $
127: */
128: public class DefaultFieldWriterFactory implements FieldWriterFactory {
129: /**
130: * Logger for this class.
131: */
132: private static final Logger logger = Logger
133: .getLogger(DefaultFieldWriterFactory.class);
134:
135: /**
136: * <copyDoc>Refer to {@link #getActionPage}.</copyDoc>
137: */
138: private String actionPage;
139: /**
140: * <p>
141: * Formatter used to format all texts.
142: * </p>
143: */
144: private HTMLFormatter formatter = new HTMLFormatter();
145: /**
146: * <p>
147: * This object is used to retrieve lists for collection field writers.
148: * </p>
149: */
150: private PersistenceManager persistenceManager;
151:
152: /**
153: * Construct a writer factory.
154: *
155: * @param persistenceManagerParam
156: * used to retrieve value objects for a value
157: * @param actionPageParam
158: * page of the action to which we'll link value objects to.
159: */
160: public DefaultFieldWriterFactory(
161: final PersistenceManager persistenceManagerParam,
162: final String actionPageParam) {
163: this .persistenceManager = persistenceManagerParam;
164: this .actionPage = actionPageParam;
165: // for now, we only have one format in the formatter
166: LineBreakFormat lineBreakFormat = new LineBreakFormat();
167: lineBreakFormat.setConvertLineBreaks(true);
168: formatter.add(lineBreakFormat);
169: }
170:
171: /**
172: * Page of the <strong>Struts </strong> action to which we'll link value
173: * objects to. This must be a full, webapp-relative link, starting with '/'.
174: *
175: * @return Returns the actionPage.
176: */
177: protected final String getActionPage() {
178: if (logger.isDebugEnabled()) {
179: logger.debug("getActionPage() - start");
180: }
181:
182: if (logger.isDebugEnabled()) {
183: logger.debug("getActionPage() - end - return value = "
184: + actionPage);
185: }
186: return actionPage;
187: }
188:
189: /**
190: * <p>
191: * Get a field writer appropriate to the given field.
192: * </p>
193: *
194: * @param session Current HTTP session we are processing.
195: * @param valueObjectParam
196: * Field for which to return an appropriate field writer.
197: * @param fieldParam
198: * Field for which to return an appropriate field writer.
199: * @param subFieldParam
200: * Sub-field within the main field, if the field is a value
201: * object.
202: * but the value stored in a hidden field.
203: * @param hidden
204: * If <code>true</code>, overrides the field definition, and gets a writer
205: * for a hidden field.
206: * @return valid field writer for the field provided.
207: * @throws SystemException
208: * thrown if the writer cannot be retrieved for any reason.
209: */
210: public final FieldWriter getFieldWriter(final HttpSession session,
211: final ValueObject valueObjectParam, final Field fieldParam,
212: final Field subFieldParam, final boolean hidden)
213: throws SystemException {
214: if (logger.isDebugEnabled()) {
215: logger.debug("getFieldWriter(HttpSession session = "
216: + session + ", ValueObject valueObjectParam = "
217: + valueObjectParam + ", Field fieldParam = "
218: + fieldParam + ", Field subFieldParam = "
219: + subFieldParam + ", boolean hidden = " + hidden
220: + ") - start");
221: }
222:
223: FieldWriter fieldWriter = null;
224: Field writerField;
225: if (subFieldParam == null) {
226: writerField = fieldParam;
227: } else {
228: writerField = subFieldParam;
229: }
230: String type = writerField.getType();
231: FieldValueConvertor fieldValueConvertor = null;
232: if (Field.TYPE_RADIO.equals(type)) {
233: throw new UnsupportedOperationException(
234: "ERROR: field type '" + type
235: + "' is not yet supported.");
236: } else if (Field.TYPE_SELECT.equals(type)) {
237: throw new UnsupportedOperationException(
238: "ERROR: field type '" + type
239: + "' is not yet supported.");
240: } else if (Field.TYPE_AMOUNT.equals(type)) {
241: fieldValueConvertor = new NumberFieldValueConvertor(
242: "###0.##");
243: } else if (Field.TYPE_NUMBER.equals(type)) {
244: fieldValueConvertor = new NumberFieldValueConvertor("###0");
245: } else if (Field.TYPE_DATE.equals(type)) {
246: fieldValueConvertor = new DateFieldValueConvertor(
247: "yyyy-MM-dd");
248: } else if (Field.TYPE_TIMESTAMP.equals(type)) {
249: fieldValueConvertor = new TimestampFieldValueConvertor();
250: } else if (Field.TYPE_STRING.equals(type)) {
251: fieldValueConvertor = new FieldValueConvertor();
252: } else {
253: // if the type was expressly set, then start looking at the class of
254: // the field
255: PropertyDescriptor descriptor;
256: try {
257: Class valueObjectClass;
258: if (subFieldParam == null) {
259: valueObjectClass = valueObjectParam.getClass();
260: } else {
261: if (fieldParam.getDOClass() != null) {
262: valueObjectClass = fieldParam.getDOClass();
263: } else {
264: descriptor = PropertyUtils
265: .getPropertyDescriptor(
266: valueObjectParam, fieldParam
267: .getPath());
268: valueObjectClass = descriptor.getPropertyType();
269: // if it is a collection, we _need_ the class
270: // specifically defined
271: if (Collection.class
272: .isAssignableFrom(valueObjectClass)) {
273: throw new NullPointerException(
274: "ERROR: you must specify a data object "
275: + "class for collection '"
276: + fieldParam.getName()
277: + "'.");
278: }
279: }
280: }
281: descriptor = getPropertyDescriptor(valueObjectClass,
282: writerField.getPath());
283: } catch (NoSuchMethodException e) {
284: logger.error(
285: "getFieldWriter - error getting property for "
286: + fieldParam, e);
287: throw new SystemException(e);
288: } catch (IllegalAccessException e) {
289: logger.error(
290: "getFieldWriter - error getting property for "
291: + fieldParam, e);
292: throw new SystemException(e);
293: } catch (InvocationTargetException e) {
294: logger.error(
295: "getFieldWriter - error getting property for "
296: + fieldParam, e);
297: throw new SystemException(e);
298: }
299: // if it is a value object, use a value object writer
300: Class propertyType = descriptor.getPropertyType();
301: if (ValueObject.class.isAssignableFrom(propertyType)
302: || Collection.class.isAssignableFrom(propertyType)) {
303: // if it is a hidden field, use the value object convertor
304: // to put the id out
305: if (hidden || writerField.isHidden()) {
306: fieldValueConvertor = new ValueObjectFieldValueConvertor();
307: } else {
308: // for a collection type, you _must_ specify the value
309: // object class to use!!!
310: Class dOClass;
311: boolean multiple = Collection.class
312: .isAssignableFrom(propertyType);
313: if (writerField.getDOClass() != null) {
314: dOClass = writerField.getDOClass();
315: } else if (multiple) {
316: throw new SystemException(
317: "ERROR: for collection field '"
318: + writerField.getPath()
319: + "', you must specify attribute 'class' in the "
320: + "ivata masks configuration.");
321: } else {
322: dOClass = propertyType;
323: }
324: PersistenceSession persistenceSession = persistenceManager
325: .openSession(session);
326: try {
327: Collection allEntries = persistenceManager
328: .findAll(persistenceSession, dOClass);
329: int listHeight = 1;
330: if (multiple) {
331: listHeight = FieldWriterConstants.MULTIPLE_LIST_HEIGHT;
332: }
333: fieldWriter = newValueObjectFieldWriter(
334: writerField, actionPage, allEntries,
335: formatter, listHeight, multiple);
336: } finally {
337: persistenceSession.close();
338: }
339: }
340: } else {
341: // default to the string representation of the field
342: fieldValueConvertor = new FieldValueConvertor();
343: }
344: }
345: // if we specified a field value convertor, then either make a
346: // hidden or text/textarea field (otherwise, it must have been the
347: // 'special case' of a value object field writer, which is dealt
348: // with above.
349: if (fieldValueConvertor != null) {
350: if (hidden || writerField.isHidden()) {
351: fieldWriter = newHiddenFieldWriter(writerField,
352: fieldValueConvertor, formatter);
353: } else if (Field.TYPE_PASSWORD.equals(type)) {
354: fieldWriter = newPasswordFieldWriter(writerField,
355: fieldValueConvertor, formatter);
356: } else if (Field.TYPE_TEXTAREA.equals(type)) {
357: fieldWriter = newTextAreaFieldWriter(writerField,
358: fieldValueConvertor, formatter);
359: } else {
360: fieldWriter = newTextFieldWriter(writerField,
361: fieldValueConvertor, formatter);
362: }
363: }
364: if (logger.isDebugEnabled()) {
365: logger.debug("getFieldWriter - end - return value = "
366: + fieldWriter);
367: }
368: return fieldWriter;
369: }
370:
371: /**
372: * <p>
373: * Find the descriptor for the class and id provided.
374: * </p>
375: *
376: * @param theClass
377: * class for which to find a descriptor.
378: * @param name
379: * name of the property for which to find a descriptor.
380: * @return valid property descriptor describing the property called
381: * <code>name</code> in class <code>theClass</code>.
382: */
383: private PropertyDescriptor getPropertyDescriptor(
384: final Class theClass, final String name) {
385: if (logger.isDebugEnabled()) {
386: logger.debug("getPropertyDescriptor(Class theClass = "
387: + theClass + ", String name = " + name
388: + ") - start");
389: }
390:
391: PropertyDescriptor[] allDescriptors = PropertyUtils
392: .getPropertyDescriptors(theClass);
393: PropertyDescriptor descriptor = null;
394: // this property might be nested
395: int dotIndex = name.indexOf('.');
396: String propertyName = null;
397: if (dotIndex == -1) {
398: propertyName = name;
399: } else {
400: propertyName = name.substring(0, dotIndex);
401: }
402: for (int i = 0; i < allDescriptors.length; i++) {
403: if (propertyName.equals(allDescriptors[i].getName())) {
404: descriptor = allDescriptors[i];
405: }
406: }
407: // if this is a nested property, get the next in the chain
408: if (dotIndex == -1) {
409: if (logger.isDebugEnabled()) {
410: logger
411: .debug("getPropertyDescriptor - end - return value = "
412: + descriptor);
413: }
414: return descriptor;
415: } else {
416: PropertyDescriptor returnPropertyDescriptor = getPropertyDescriptor(
417: theClass, name.substring(++dotIndex));
418: if (logger.isDebugEnabled()) {
419: logger
420: .debug("getPropertyDescriptor - end - return value = "
421: + returnPropertyDescriptor);
422: }
423: return returnPropertyDescriptor;
424: }
425: }
426:
427: /**
428: * Override this method if you need a different field writer for passwords.
429: *
430: * @param field
431: * <copyDoc>Refer to {@link TextAreaFieldWriter#TextAreaFieldWriter}.
432: * </copyDoc>
433: * @param convertor
434: * <copyDoc>Refer to {@link TextAreaFieldWriter#TextAreaFieldWriter}.
435: * </copyDoc>
436: * @param formatterParam
437: * <copyDoc>Refer to {@link TextAreaFieldWriter#TextAreaFieldWriter}.
438: * </copyDoc>
439: * @return valid text area field writer.
440: */
441: protected FieldWriter newPasswordFieldWriter(final Field field,
442: final FieldValueConvertor convertor,
443: final HTMLFormatter formatterParam) {
444: if (logger.isDebugEnabled()) {
445: logger.debug("newPasswordFieldWriter(Field field = "
446: + field + ", FieldValueConvertor convertor = "
447: + convertor + ", HTMLFormatter formatterParam = "
448: + formatterParam + ") - start");
449: }
450:
451: FieldWriter returnFieldWriter = new PasswordFieldWriter(field,
452: convertor, formatterParam);
453: if (logger.isDebugEnabled()) {
454: logger
455: .debug("newPasswordFieldWriter - end - return value = "
456: + returnFieldWriter);
457: }
458: return returnFieldWriter;
459: }
460:
461: /**
462: * Override this method if you need a different field writer for text areas.
463: *
464: * @param field
465: * <copyDoc>Refer to {@link TextAreaFieldWriter#TextAreaFieldWriter}.
466: * </copyDoc>
467: * @param convertor
468: * <copyDoc>Refer to {@link TextAreaFieldWriter#TextAreaFieldWriter}.
469: * </copyDoc>
470: * @param formatterParam
471: * <copyDoc>Refer to {@link TextAreaFieldWriter#TextAreaFieldWriter}.
472: * </copyDoc>
473: * @return valid text area field writer.
474: */
475: protected FieldWriter newTextAreaFieldWriter(final Field field,
476: final FieldValueConvertor convertor,
477: final HTMLFormatter formatterParam) {
478: if (logger.isDebugEnabled()) {
479: logger.debug("newTextAreaFieldWriter(Field field = "
480: + field + ", FieldValueConvertor convertor = "
481: + convertor + ", HTMLFormatter formatterParam = "
482: + formatterParam + ") - start");
483: }
484:
485: FieldWriter returnFieldWriter = new TextAreaFieldWriter(field,
486: convertor, formatterParam);
487: if (logger.isDebugEnabled()) {
488: logger
489: .debug("newTextAreaFieldWriter - end - return value = "
490: + returnFieldWriter);
491: }
492: return returnFieldWriter;
493: }
494:
495: /**
496: * Override this method if you need a different field writer for hidden
497: * fields.
498: *
499: * @param fieldParam
500: * <copyDoc>Refer to {@link TextFieldWriter#TextFieldWriter}.</copyDoc>
501: * @param convertorParam
502: * <copyDoc>Refer to {@link TextFieldWriter#TextFieldWriter}.</copyDoc>
503: * @param formatterParam
504: * <copyDoc>Refer to {@link TextFieldWriter#TextFieldWriter}.</copyDoc>
505: * @return valid text field writer.
506: */
507: protected FieldWriter newHiddenFieldWriter(final Field fieldParam,
508: final FieldValueConvertor convertorParam,
509: final HTMLFormatter formatterParam) {
510: if (logger.isDebugEnabled()) {
511: logger.debug("newHiddenFieldWriter(Field fieldParam = "
512: + fieldParam
513: + ", FieldValueConvertor convertorParam = "
514: + convertorParam
515: + ", HTMLFormatter formatterParam = "
516: + formatterParam + ") - start");
517: }
518:
519: FieldWriter returnFieldWriter = new HiddenFieldWriter(
520: fieldParam, convertorParam, formatterParam);
521: if (logger.isDebugEnabled()) {
522: logger.debug("newHiddenFieldWriter - end - return value = "
523: + returnFieldWriter);
524: }
525: return returnFieldWriter;
526: }
527:
528: /**
529: * Override this method if you need a different field writer for text
530: * fields.
531: *
532: * @param fieldParam
533: * <copyDoc>Refer to {@link TextFieldWriter#TextFieldWriter}.</copyDoc>
534: * @param convertorParam
535: * <copyDoc>Refer to {@link TextFieldWriter#TextFieldWriter}.</copyDoc>
536: * @param formatterParam
537: * <copyDoc>Refer to {@link TextFieldWriter#TextFieldWriter}.</copyDoc>
538: * @return valid text field writer.
539: */
540: protected FieldWriter newTextFieldWriter(final Field fieldParam,
541: final FieldValueConvertor convertorParam,
542: final HTMLFormatter formatterParam) {
543: if (logger.isDebugEnabled()) {
544: logger.debug("newTextFieldWriter(Field fieldParam = "
545: + fieldParam
546: + ", FieldValueConvertor convertorParam = "
547: + convertorParam
548: + ", HTMLFormatter formatterParam = "
549: + formatterParam + ") - start");
550: }
551:
552: FieldWriter returnFieldWriter = new TextFieldWriter(fieldParam,
553: convertorParam, formatterParam);
554: if (logger.isDebugEnabled()) {
555: logger.debug("newTextFieldWriter - end - return value = "
556: + returnFieldWriter);
557: }
558: return returnFieldWriter;
559: }
560:
561: /**
562: * Override this method if you need a different field writer for value
563: * objects.
564: *
565: * @param fieldParam
566: * <copyDoc>Refer to {@link ValueObjectFieldWriter#ValueObjectFieldWriter}.
567: * </copyDoc>
568: * @param actionPageParam
569: * <copyDoc>Refer to {@link ValueObjectFieldWriter#ValueObjectFieldWriter}.
570: * </copyDoc>
571: * @param allValueObjectsParam
572: * <copyDoc>Refer to {@link ValueObjectFieldWriter#ValueObjectFieldWriter}.
573: * </copyDoc>
574: * @param formatterParam
575: * <copyDoc>Refer to {@link ValueObjectFieldWriter#ValueObjectFieldWriter}.
576: * </copyDoc>
577: * @param listHeightParam
578: * <copyDoc>Refer to {@link ValueObjectFieldWriter#ValueObjectFieldWriter}.
579: * </copyDoc>
580: * @param multipleParam
581: * <copyDoc>Refer to {@link ValueObjectFieldWriter#ValueObjectFieldWriter}.
582: * </copyDoc>
583: * @return valid field writer.
584: */
585: protected FieldWriter newValueObjectFieldWriter(
586: final Field fieldParam, final String actionPageParam,
587: final Collection allValueObjectsParam,
588: final HTMLFormatter formatterParam,
589: final int listHeightParam, final boolean multipleParam) {
590: if (logger.isDebugEnabled()) {
591: logger
592: .debug("newValueObjectFieldWriter(Field fieldParam = "
593: + fieldParam
594: + ", String actionPageParam = "
595: + actionPageParam
596: + ", Collection allValueObjectsParam = "
597: + allValueObjectsParam
598: + ", HTMLFormatter formatterParam = "
599: + formatterParam
600: + ", int listHeightParam = "
601: + listHeightParam
602: + ", boolean multipleParam = "
603: + multipleParam + ") - start");
604: }
605:
606: FieldWriter returnFieldWriter = new ValueObjectFieldWriter(
607: fieldParam, actionPageParam, allValueObjectsParam,
608: formatterParam, listHeightParam, multipleParam);
609: if (logger.isDebugEnabled()) {
610: logger
611: .debug("newValueObjectFieldWriter - end - return value = "
612: + returnFieldWriter);
613: }
614: return returnFieldWriter;
615: }
616: }
|