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: DefaultMaskFactory.java,v $
31   * Revision 1.8  2005/04/11 12:27:02  colinmacleod
32   * Added preliminary support for filters.
33   * Added FieldValueConvertor factor interface
34   * to split off value convertors for reuse.
35   *
36   * Revision 1.7  2005/04/09 18:04:14  colinmacleod
37   * Changed copyright text to GPL v2 explicitly.
38   *
39   * Revision 1.6  2005/03/10 10:25:56  colinmacleod
40   * Added getClass() method with defaulted name.
41   *
42   * Revision 1.5  2005/01/19 12:39:39  colinmacleod
43   * Changed Id --> Name.
44   *
45   * Revision 1.4  2005/01/07 13:08:24  colinmacleod
46   * Fixed default input and list masks - they were both
47   * still returning a hard-coded string.
48   *
49   * Revision 1.3  2005/01/06 22:13:21  colinmacleod
50   * Moved up a version number.
51   * Changed copyright notices to 2005.
52   * Updated the documentation:
53   *   - started working on multiproject:site docu.
54   *   - changed the logo.
55   * Added checkstyle and fixed LOADS of style issues.
56   * Added separate thirdparty subproject.
57   * Added struts (in web), util and webgui (in webtheme) from ivata op.
58   *
59   * Revision 1.2  2004/12/30 20:13:12  colinmacleod
60   * Added reflection to let you override the field writer classes used.
61   *
62   * Revision 1.1  2004/12/29 20:07:06  colinmacleod
63   * Renamed subproject masks to mask.
64   *
65   * Revision 1.3  2004/12/29 15:27:06  colinmacleod
66   * Added methods to get default input/list masks.
67   * Removed final to allow overrides to change default masks.
68   *
69   * Revision 1.2  2004/11/11 13:34:22  colinmacleod
70   * Added addDefaultFields method.
71   *
72   * Revision 1.1.1.1  2004/05/16 20:40:31  colinmacleod
73   * Ready for 0.1 release
74   * -----------------------------------------------------------------------------
75   */
76  package com.ivata.mask;
77  import java.beans.PropertyDescriptor;
78  import java.io.IOException;
79  import java.io.InputStream;
80  import java.io.Serializable;
81  import java.math.BigDecimal;
82  import java.util.ArrayList;
83  import java.util.Date;
84  import java.util.HashMap;
85  import java.util.Iterator;
86  import java.util.List;
87  import java.util.Map;
88  import java.util.Properties;
89  import org.apache.commons.beanutils.PropertyUtils;
90  import org.dom4j.Document;
91  import org.dom4j.DocumentException;
92  import org.dom4j.Element;
93  import org.dom4j.io.SAXReader;
94  import org.xml.sax.InputSource;
95  import com.ivata.mask.field.Field;
96  import com.ivata.mask.field.FieldImpl;
97  import com.ivata.mask.field.FieldValueConvertor;
98  import com.ivata.mask.field.FieldValueConvertorFactory;
99  import com.ivata.mask.filter.Filter;
100 import com.ivata.mask.filter.FilterImpl;
101 import com.ivata.mask.group.Group;
102 import com.ivata.mask.group.GroupImpl;
103 import com.ivata.mask.util.StringHandling;
104 import com.ivata.mask.util.SystemException;
105 /***
106  * <p>
107  * This factory class is at the heart of ivata masks. Use it to read in a
108  * configuration file (in XML), and then access groups of fields via their
109  * unique identifiers.
110  * </p>
111  *
112  * <p>
113  * It is called <code>DefaultMaskFactory</code> because the <strong>ivata
114  * masks </strong> system actually never refers to this class directly - it uses
115  * the interface {@link MaskFactory}, meaning you could create your own factory
116  * implementation, if you want to.
117  * </p>
118  *
119  * @author Colin MacLeod
120  * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
121  * @since ivata masks 0.1 (2004-02-26)
122  */
123 public final class DefaultMaskFactory implements MaskFactory, Serializable {
124     /***
125      * <p>
126      * The name of the default mask/screen used for user input.
127      * </p>
128      */
129     private String defaultInputMask;
130     /***
131      * <p>
132      * The name of the default mask/screen used to list.
133      * </p>
134      */
135     private String defaultListMask;
136     /***
137      * Used to create convertors to convert field values in the filters.
138      */
139     private FieldValueConvertorFactory fieldValueConvertorFactory;
140     /***
141      * Mapping of all config groups, mapped by identifier.
142      */
143     private Map groups;
144     /***
145      * <p>
146      * Default constructor. Initializes the mask factory with
147      * &quot;inputMask&quot; as the default input mask, and
148      * &quot;inputMask&quot; as the default list mask.
149      * </p>
150      * @param fieldValueConvertorFactory creates convertors to convert field
151      * values in the filters.
152      */
153     public DefaultMaskFactory(final FieldValueConvertorFactory
154             fieldValueConvertorFactory) {
155         this("inputMask", "listMask", fieldValueConvertorFactory);
156     }
157     /***
158      * <p>
159      * Construct an instance of the factory with the default mask/screens
160      * provided.
161      * </p>
162      *
163      * @param defaultInputMaskParam
164      *            The name of the default mask/screen used for user input.
165      * @param defaultListMaskParam
166      *            The name of the default mask/screen used to list.
167      * @param fieldValueConvertorFactory creates convertors to convert field
168      * values in the filters.
169      */
170     public DefaultMaskFactory(final String defaultInputMaskParam,
171             final String defaultListMaskParam,
172             final FieldValueConvertorFactory fieldValueConvertorFactoryParam) {
173         this.defaultInputMask = defaultInputMaskParam;
174         this.defaultListMask = defaultListMaskParam;
175         this.fieldValueConvertorFactory = fieldValueConvertorFactoryParam;
176     }
177     /***
178      * <p>
179      * Go thro' all the properties of the value object class and add fields for
180      * those properties which were not explicitly defined in the configuration
181      * file.
182      * </p>
183      *
184      * @param mask
185      *            Mask for which to add all the default fields
186      * @param parentField
187      *            If this mask applies to a field within another mask, (known as
188      *            a submask) this is the field to which it applies, otherwise
189      *            <code>null</code>.
190      */
191     private void addDefaultFields(final MaskImpl mask,
192             final Field parentField) {
193         Class dOClass = mask.getDOClass();
194         PropertyDescriptor[] descriptors = PropertyUtils
195                 .getPropertyDescriptors(dOClass);
196         for (int i = 0; i < descriptors.length; i++) {
197             PropertyDescriptor descriptor = descriptors[i];
198             String fieldName = descriptor.getName();
199             // ignore getClass() from Object
200             if ("class".equals(fieldName)) {
201                 continue;
202             }
203             StringBuffer combinedName = new StringBuffer();
204             if (parentField != null) {
205                 combinedName.append(parentField.getPath());
206                 combinedName.append(".");
207             }
208             combinedName.append(fieldName);
209             if (mask.getField(combinedName.toString()) == null) {
210                 FieldImpl field = new FieldImpl(parentField, mask
211                         .getField(fieldName), this);
212                 field.setName(fieldName);
213                 Class fieldClass = descriptor.getPropertyType();
214                 // guess the field type
215                 if (Date.class.isAssignableFrom(fieldClass)) {
216                     field.setType(Field.TYPE_DATE);
217                 } else if (BigDecimal.class.isAssignableFrom(fieldClass)
218                         || Double.class.isAssignableFrom(fieldClass)) {
219                     field.setType(Field.TYPE_AMOUNT);
220                 } else if (Integer.class.isAssignableFrom(fieldClass)) {
221                     field.setType(Field.TYPE_NUMBER);
222                 } else if (String.class.isAssignableFrom(fieldClass)) {
223                     field.setType(Field.TYPE_STRING);
224                 } else {
225                     field.setType(null);
226                 }
227                 mask.addField(field);
228             }
229         }
230     }
231     /***
232      * Extract a field from a dom4j element.
233      *
234      * @param element
235      *            dom4j element which represents a field.
236      * @param group
237      *            group which will contain this field.
238      * @return field New field represented by the element provided.
239      * TODO: replace NullPointerException thrown here with a mask configuration
240      * exception.
241      */
242     private Field extractField(final Group group, final Element element) {
243         // the field can either extend another one in this group explicitly,
244         // or a field in a parent group implicitly
245         String extendsField = element.attributeValue("extends");
246         String name = element.attributeValue("name");
247         if (name == null) {
248             throw new NullPointerException("ERROR in mask configuration: "
249                     + "mandatory name attribute null for field.");
250         }
251         Field extendedField;
252         if (extendsField == null) {
253             // look for a field of the same id which already exists in this
254             // group or a parent
255             extendedField = group.getField(name);
256         } else {
257             // look for a field with the extended name
258             extendedField = group.getField(extendsField);
259             // in this case, it is an error if the field is <code>null</code>
260             if (extendedField == null) {
261                 throw new NullPointerException("ERROR in mask configuration: "
262                         + "field '" + name + "' extends unknown field '"
263                         + extendsField + "'");
264             }
265         }
266         FieldImpl field = new FieldImpl(null, extendedField, this);
267         field.setName(name);
268         String type = element.attributeValue("type");
269         field.setType(type);
270 
271         String displayOnly = element.attributeValue("displayOnly");
272         field.setDisplayOnly("true".equalsIgnoreCase(displayOnly));
273         String hidden = element.attributeValue("hidden");
274         field.setHidden("true".equalsIgnoreCase(hidden));
275         String mandatory = element.attributeValue("mandatory");
276         field.setMandatory("true".equalsIgnoreCase(mandatory));
277         String oneToOne = element.attributeValue("oneToOne");
278         field.setOneToOne("true".equalsIgnoreCase(oneToOne));
279         String defaultValue = element.attributeValue("default");
280         field.setDefaultValue(defaultValue);
281         String labelKey = element.attributeValue("labelKey");
282         field.setLabelKey(labelKey);
283         String className = element.attributeValue("class");
284         // if a class name is set, find and set the dependent value object
285         if (className != null) {
286             try {
287                 Class dOClass = Class.forName(className);
288                 field.setDOClass(dOClass);
289             } catch (ClassNotFoundException e) {
290                 throw new RuntimeException("ERROR (" + e.getClass()
291                         + ") cannot locate class: " + className + ": "
292                         + e.getMessage());
293             }
294         }
295         List choiceList = element.selectNodes("choice");
296         Properties choiceProperties = field.getChoiceProperties();
297         if (choiceList.size() > 0) {
298             choiceProperties = new Properties();
299             List choicePropertyKeys = new ArrayList();
300             for (Iterator iterator = choiceList.iterator();
301                     iterator.hasNext();) {
302                 Element choice = (Element) iterator.next();
303                 String key = choice.attributeValue("value");
304                 String text = choice.getText();
305                 if (key == null) {
306                     key = text;
307                 }
308                 choiceProperties.setProperty(key, text);
309                 choicePropertyKeys.add(key);
310             }
311             field.setChoiceProperties(choiceProperties);
312             field.setChoicePropertyKeys(choicePropertyKeys);
313         }
314         // for select or radio types, there must either be a value object class
315         if (("select".equals(type) || "radio".equals(type))
316                 && (className == null) && (choiceProperties == null)) {
317             throw new RuntimeException(
318                 "ERROR in mask configuration: "
319                 + "you must specify either choices or value object class for "
320                 + "field " + type + ", name '" + name + "'");
321         }
322         return field;
323     }
324     /***
325      * Extract a singel filter from the group provided.
326      *
327      * @param group parent group surrounding the filter.
328      * @param element the document element from which to extract the filter.
329      * @return the extracted filter, represented by the XML in the
330      * <code>element</code>.
331      */
332     private Filter extractFilter(final Group groupParam,
333             final Element element) {
334         String propertyName = element.attributeValue("propertyName");
335         String stringValue = element.attributeValue("value");
336         String className = element.attributeValue("propertyClass");
337         Class propertyClass;
338         try {
339             propertyClass = Class.forName(className);
340         } catch (ClassNotFoundException e) {
341             throw new RuntimeException(e);
342         }
343         FieldValueConvertor convertor;
344         try {
345             convertor = fieldValueConvertorFactory
346                 .getFieldValueConvertorForClass(propertyClass);
347         } catch (SystemException e) {
348             throw new RuntimeException(e);
349         }
350         Object value = convertor.convertFromString(propertyClass,
351                 stringValue);
352         return new FilterImpl(propertyName, propertyClass, value);
353     }
354     /***
355      * Extracts a single group from the element provided.
356      *
357      * @param element
358      *            The document element from which to extract the group.
359      * @return New group represented by the XML in <code>element</code>.
360      * TODO: replace NullPointerException thrown here with a mask configuration
361      * exception.
362      */
363     private Group extractGroup(final Element element) {
364         // the group can extend another one explicitly
365         String extendsGroup = element.attributeValue("extends");
366         String name = element.attributeValue("name");
367         if (StringHandling.isNullOrEmpty(name)) {
368             throw new NullPointerException("ERROR in mask configuration: "
369                     + "mandatory name attribute null for group.");
370         }
371         Group parent = null;
372         if (extendsGroup != null) {
373             parent = (Group) groups.get(extendsGroup);
374             if (parent == null) {
375                 throw new NullPointerException("ERROR in mask configuration: "
376                         + "group '" + name + "' extends unknown group '"
377                         + extendsGroup + "'");
378             }
379         }
380         GroupImpl group = new GroupImpl(name, parent);
381         // groups contain field definitions and masks - first the fields
382         extractGroupFields(element, group);
383         // now extract the masks
384         for (Iterator i = element.elementIterator("mask"); i.hasNext();) {
385             Element maskElement = (Element) i.next();
386             Mask mask = extractMask(group, maskElement);
387             String maskId = getMaskId(mask.getDOClass().getName(),
388                     mask.getName());
389             groups.put(maskId, mask);
390         }
391         return group;
392     }
393     /***
394      * This section is used by both <code>extractMask</code> and
395      * <code>extractGroup</code>.
396      *
397      * @param element
398      *            dom4j element to extract the information from.
399      * @param group
400      *            group or mask to set the information into.
401      */
402     private void extractGroupFields(final Element element,
403             final GroupImpl group) {
404         for (Iterator i = element.elementIterator("field"); i.hasNext();) {
405             Element fieldElement = (Element) i.next();
406             Field field = extractField(group, fieldElement);
407             group.addField(field);
408         }
409         // find excluded fields
410         Element exclude = element.element("exclude");
411         if (exclude != null) {
412             for (Iterator iter = exclude.elementIterator("fieldName"); iter
413                     .hasNext();) {
414                 Element fieldNameElement = (Element) iter.next();
415                 group.addExcludedFieldName(fieldNameElement.getTextTrim());
416             }
417         }
418         // find included fields
419         Element include = element.element("include");
420         if (include != null) {
421             for (Iterator iter = include.elementIterator("fieldName"); iter
422                     .hasNext();) {
423                 Element fieldNameElement = (Element) iter.next();
424                 group.addIncludedFieldName(fieldNameElement.getTextTrim());
425             }
426         }
427         // if this group is marked display only, note that
428         String displayOnly = element.attributeValue("displayOnly");
429         if (displayOnly != null) {
430             group.setDisplayOnly("true".equals(displayOnly));
431         }
432         // find fields at start and end
433         Element first = element.element("first");
434         if (first != null) {
435             for (Iterator iter = first.elementIterator("fieldName"); iter
436                     .hasNext();) {
437                 Element fieldNameElement = (Element) iter.next();
438                 group.addFirstFieldName(fieldNameElement.getText());
439             }
440         }
441         Element last = element.element("last");
442         if (last != null) {
443             for (Iterator iter = last.elementIterator("fieldName"); iter
444                     .hasNext();) {
445                 Element fieldNameElement = (Element) iter.next();
446                 group.addLastFieldName(fieldNameElement.getText());
447             }
448         }
449         // filters
450         for (Iterator i = element.elementIterator("filter"); i.hasNext();) {
451             Element filterElement = (Element) i.next();
452             Filter filter = extractFilter(group, filterElement);
453             group.addFilter(filter);
454         }
455     }
456     /***
457      * Extracts a single mask from the mask element provided.
458      *
459      * @param group
460      *            The parent group surrounding the mask.
461      * @param element
462      *            The document element from which to extract the mask.
463      * @return New mask represented by the XML in <code>element</code>.
464      * TODO: replace NullPointerException thrown here with a mask configuration
465      * exception.
466      */
467     private Mask extractMask(final Group group, final Element element) {
468         String name = element.attributeValue("name");
469         String className = element.attributeValue("valueObject");
470         // you must always supply a class name for a mask
471         if (className == null) {
472             throw new NullPointerException("ERROR in mask configuration: "
473                     + "mandatory 'valueObject' attribute null for mask'"
474                     + name
475                     + "'.");
476         }
477         // the mask can extend another one explicitly, or a containing group
478         // implicitly
479         String extendsMask = element.attributeValue("extends");
480         Group parent = null;
481         if (extendsMask != null) {
482             // first try extends as a reference to another mask..
483             String id = getMaskId(className, extendsMask);
484             parent = (Group) groups.get(id);
485             // if there is no mask with this combination, look for a group with
486             // the extended name
487             if (parent == null) {
488                 parent = (Group) groups.get(extendsMask);
489                 if (parent == null) {
490                     throw new NullPointerException(
491                             "ERROR in mask configuration: mask '"
492                             + name
493                             + "' extends unknown mask/group '"
494                             + extendsMask
495                             + "'");
496                 }
497             }
498         } else {
499             // if there is no explicit extends, it extends from the parent
500             // group around it
501             parent = group;
502         }
503         // now get the dependent value object class
504         Class dOClass;
505         try {
506             dOClass = Class.forName(className);
507         } catch (ClassNotFoundException e) {
508             throw new RuntimeException("ERROR (" + e.getClass()
509                     + ") cannot locate class: " + className + ": "
510                     + e.getMessage());
511         }
512         MaskImpl mask = new MaskImpl(dOClass, parent, name);
513         // this section is shared with group
514         extractGroupFields(element, mask);
515         addDefaultFields(mask, null);
516 
517         // extract any include paths
518         for (Iterator i = element.elementIterator("include"); i.hasNext();) {
519             Element includeElement = (Element) i.next();
520             String path = includeElement.attributeValue("path");
521             if (StringHandling.isNullOrEmpty(path)) {
522                 path = includeElement.getTextTrim();
523             }
524             if (StringHandling.isNullOrEmpty(path)) {
525                 throw new NullPointerException("ERROR in mask configuration: "
526                         + "you must specify either a path or body content "
527                         + "for all includes in mask '"
528                         + mask.getName()
529                         + "'.");
530             }
531             mask.addIncludePath(path);
532         }
533         return mask;
534     }
535     /***
536      * <p>
537      * Get the name of the default mask/screen used for user input.
538      * </p>
539      *
540      * @return name of the default mask/screen used for user input.
541      * @see com.ivata.mask.MaskFactory#getDefaultInputMask()
542      */
543     public String getDefaultInputMask() {
544         // by default just return the name "inputMask"
545         return defaultInputMask;
546     }
547     /***
548      * <p>
549      * Get the name of the default mask/screen used for lists.
550      * </p>
551      *
552      * @return name of the default mask/screen used for lists.
553      * @see com.ivata.mask.MaskFactory#getDefaultListMask()
554      */
555     public String getDefaultListMask() {
556         // by default just return the name "listMask"
557         return defaultListMask;
558     }
559     /***
560      * <p>
561      * Get a group definition referenced by its id.
562      * </p>
563      *
564      * @param id
565      *            unique identifier of the group.
566      * @return Group definition with the id provided, or <code>null</code> if
567      *         there is no such group.
568      */
569     public Group getGroup(final String id) {
570         if (!isConfigured()) {
571             throw new RuntimeException(
572                     "ERROR in MaskFactory: you must first read in configuration"
573                             + " by calling readConfiguration.");
574         }
575         return (Group) groups.get(id);
576     }
577     /***
578      * This will return the <u>default input mask</u> for the class provided.
579      * Refer to {@link MaskFactory#getMask}.
580      *
581      * @param valueObjectClassParam Refer to {@link MaskFactory#getMask}.
582      * @return Refer to {@link MaskFactory#getMask}.
583      */
584     public Mask getMask(final Class valueObjectClassParam) {
585         return getMask(valueObjectClassParam, getDefaultInputMask());
586     }
587     /***
588      * <p>
589      * Get a mask, identified by its class and name.
590      * </p>
591      *
592      * @param valueObjectClass
593      *            class of value object for the mask to be returned.
594      * @param name
595      *            optional parameter defining multiple masks for the same value
596      *            object. May be <code>null</code>.
597      * @return Mask definition with the id provided, or <code>null</code> if
598      *         there is no such mask.
599      */
600     public Mask getMask(final Class valueObjectClass, final String name) {
601         return getMask(null, valueObjectClass, name);
602     }
603     /***
604      * This will return the <u>default input mask</u> for the class provided
605      * of the subclassed field.
606      * Refer to {@link MaskFactory#getMask}.
607      *
608      * @param valueObjectClassParam Refer to {@link MaskFactory#getMask}.
609      * @return Refer to {@link MaskFactory#getMask}.
610      */
611     public Mask getMask(final Field parentField,
612             final Class valueObjectClassParam) {
613         return getMask(parentField, valueObjectClassParam,
614                 getDefaultInputMask());
615     }
616     /***
617      * <p>
618      * Get a mask, identified by its class and name.
619      * </p>
620      *
621      * @param parentField
622      *            If this mask applies to a field within another mask, (known as
623      *            a submask) this is the field to which it applies, otherwise
624      *            use the other <code>getMask</code> method.
625      * @param valueObjectClass
626      *            class of value object for the mask to be returned.
627      * @param nameParam
628      *            describes this mask uniquely within the value object. (You
629      * can have more than one mask for each value object.)
630      * @return Mask definition with the id provided, or <code>null</code> if
631      *         there is no such mask.
632      * TODO: replace NullPointerException thrown here with a mask configuration
633      * exception.
634      */
635     public Mask getMask(final Field parentField, final Class valueObjectClass,
636             final String nameParam) {
637         StringBuffer combinedName = new StringBuffer();
638         if (parentField != null) {
639             combinedName.append(parentField.getPath());
640             combinedName.append(".");
641         }
642         combinedName.append(valueObjectClass.getName());
643         String id = getMaskId(combinedName.toString(), nameParam);
644         Group group = getGroup(id);
645         if (group == null) {
646             // first see if there is a mask defined for this field. If so,
647             // we'll extend this one to make the sublist mask
648             String parentId = getMaskId(valueObjectClass.getName(), nameParam);
649             Group parent = getGroup(parentId);
650             // if there is no mask defined, create a default mask from the group
651             if (parent == null) {
652                 parent = getGroup(nameParam);
653                 if (parent == null) {
654                     throw new NullPointerException(
655                         "ERROR: no appropriate mask or group called '"
656                             + nameParam
657                             + "' for class '"
658                             + valueObjectClass.getName()
659                             + "'.");
660                 }
661             }
662 
663             // if not, create a default mask in the parent group
664             MaskImpl defaultMask = new MaskImpl(valueObjectClass, parent,
665                     nameParam);
666             addDefaultFields(defaultMask, parentField);
667             groups.put(combinedName.toString(), defaultMask);
668             return defaultMask;
669         }
670         if (!(group instanceof Mask)) {
671             throw new RuntimeException("ERROR: the group '"
672                     + id
673                     + "' does not represent a mask.");
674         }
675         return (Mask) group;
676     }
677     /***
678      * <p>
679      * Create a unique identifier for the mask, based on the class and name.
680      * For groups, we use the name alone as unique.
681      * </p>
682      *
683      * @param className
684      *            name of the class of value object for the mask to be returned.
685      * @param name
686      *            describes this mask uniquely within the value object. (You
687      * can have more than one mask for each value object.)
688      * @return Unique identifier for the mask based on class name and mask name.
689      */
690     private String getMaskId(final String className, final String name) {
691         if (name == null) {
692             return className;
693         } else {
694             return className + "_" + name;
695         }
696     }
697     /***
698      * <p>
699      * Discover whether or not this object has been configured.
700      * </p>
701      *
702      * @return <code>true</code> if the object has been configured, otherwise
703      *         <code>false</code>.
704      * @see com.ivata.mask.MaskFactory#isConfigured()
705      */
706     public boolean isConfigured() {
707         // if there are no groups, assume it wasn't configured
708         return groups != null;
709     }
710     /***
711      * <p>
712      * Read in the configuration represented by the <strong>dom4j </strong>
713      * document provided.
714      * </p>
715      *
716      * @param document
717      *            <strong>dom4j </strong> document to read configuration from.
718      * @throws IOException
719      *             If there is any problem reading from the stream provided.
720      */
721     private synchronized void readConfiguration(final Document document)
722             throws IOException {
723         Element root = document.getRootElement();
724         groups = new HashMap();
725         for (Iterator i = root.elementIterator("group"); i.hasNext();) {
726             Element groupElement = (Element) i.next();
727             Group group = extractGroup(groupElement);
728             groups.put(group.getName(), group);
729         }
730     }
731     /***
732      * Get the configuration represented by the <i>dom4j </i> document provided.
733      *
734      * @param inputStream
735      *            The input stream to read the XML from.
736      * @throws IOException
737      *             If there is any problem reading from the stream provided.
738      */
739     public void readConfiguration(final InputStream inputStream)
740             throws IOException {
741         SAXReader reader = new SAXReader();
742         Document document;
743         try {
744             InputSource is = new InputSource();
745             is.setByteStream(inputStream);
746             document = reader.read(is);
747         } catch (DocumentException e) {
748             e.printStackTrace();
749             throw new IOException("ERROR in MaskConfigurationFactory: "
750                     + e.getMessage());
751         }
752         readConfiguration(document);
753     }
754 }