0001: /*
0002: * Copyright (c) 2001 - 2005 ivata limited.
0003: * All rights reserved.
0004: * -----------------------------------------------------------------------------
0005: * ivata masks may be redistributed under the GNU General Public
0006: * License as published by the Free Software Foundation;
0007: * version 2 of the License.
0008: *
0009: * These programs are free software; you can redistribute them and/or
0010: * modify them under the terms of the GNU General Public License
0011: * as published by the Free Software Foundation; version 2 of the License.
0012: *
0013: * These programs are distributed in the hope that they will be useful,
0014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0016: *
0017: * See the GNU General Public License in the file LICENSE.txt for more
0018: * details.
0019: *
0020: * If you would like a copy of the GNU General Public License write to
0021: *
0022: * Free Software Foundation, Inc.
0023: * 59 Temple Place - Suite 330
0024: * Boston, MA 02111-1307, USA.
0025: *
0026: *
0027: * To arrange commercial support and licensing, contact ivata at
0028: * http://www.ivata.com/contact.jsp
0029: * -----------------------------------------------------------------------------
0030: * $Log: DefaultMaskFactory.java,v $
0031: * Revision 1.15 2005/10/12 18:20:54 colinmacleod
0032: * Fixed some style issues.
0033: *
0034: * Revision 1.14 2005/10/11 18:55:29 colinmacleod
0035: * Fixed some checkstyle and javadoc issues.
0036: *
0037: * Revision 1.13 2005/10/03 10:17:24 colinmacleod
0038: * Fixed some style and javadoc issues.
0039: *
0040: * Revision 1.12 2005/10/02 14:06:32 colinmacleod
0041: * Added/improved log4j logging.
0042: *
0043: * Revision 1.11 2005/09/29 12:11:56 colinmacleod
0044: * Added checking that the class referenced is available - throws a warning
0045: * if not, but carries on working (makes it easier to re-use a common
0046: * settings file).
0047: *
0048: * Revision 1.10 2005/09/16 13:41:18 colinmacleod
0049: * Created new convertor class to handle timestamps.
0050: *
0051: * Revision 1.9 2005/09/14 12:51:22 colinmacleod
0052: * Added serialVersionUID.
0053: * Changed XPath expression for choice.
0054: *
0055: * Revision 1.8 2005/04/11 12:27:02 colinmacleod
0056: * Added preliminary support for filters.
0057: * Added FieldValueConvertor factor interface
0058: * to split off value convertors for reuse.
0059: *
0060: * Revision 1.7 2005/04/09 18:04:14 colinmacleod
0061: * Changed copyright text to GPL v2 explicitly.
0062: *
0063: * Revision 1.6 2005/03/10 10:25:56 colinmacleod
0064: * Added getClass() method with defaulted name.
0065: *
0066: * Revision 1.5 2005/01/19 12:39:39 colinmacleod
0067: * Changed Id --> Name.
0068: *
0069: * Revision 1.4 2005/01/07 13:08:24 colinmacleod
0070: * Fixed default input and list masks - they were both
0071: * still returning a hard-coded string.
0072: *
0073: * Revision 1.3 2005/01/06 22:13:21 colinmacleod
0074: * Moved up a version number.
0075: * Changed copyright notices to 2005.
0076: * Updated the documentation:
0077: * - started working on multiproject:site docu.
0078: * - changed the logo.
0079: * Added checkstyle and fixed LOADS of style issues.
0080: * Added separate thirdparty subproject.
0081: * Added struts (in web), util and webgui (in webtheme) from ivata op.
0082: *
0083: * Revision 1.2 2004/12/30 20:13:12 colinmacleod
0084: * Added reflection to let you override the field writer classes used.
0085: *
0086: * Revision 1.1 2004/12/29 20:07:06 colinmacleod
0087: * Renamed subproject masks to mask.
0088: *
0089: * Revision 1.3 2004/12/29 15:27:06 colinmacleod
0090: * Added methods to get default input/list masks.
0091: * Removed final to allow overrides to change default masks.
0092: *
0093: * Revision 1.2 2004/11/11 13:34:22 colinmacleod
0094: * Added addDefaultFields method.
0095: *
0096: * Revision 1.1.1.1 2004/05/16 20:40:31 colinmacleod
0097: * Ready for 0.1 release
0098: * -----------------------------------------------------------------------------
0099: */
0100: package com.ivata.mask;
0101:
0102: import java.beans.PropertyDescriptor;
0103: import java.io.IOException;
0104: import java.io.InputStream;
0105: import java.io.Serializable;
0106: import java.math.BigDecimal;
0107: import java.sql.Timestamp;
0108: import java.util.ArrayList;
0109: import java.util.Date;
0110: import java.util.HashMap;
0111: import java.util.Iterator;
0112: import java.util.List;
0113: import java.util.Map;
0114: import java.util.Properties;
0115:
0116: import org.apache.commons.beanutils.PropertyUtils;
0117: import org.apache.log4j.Logger;
0118: import org.dom4j.Document;
0119: import org.dom4j.DocumentException;
0120: import org.dom4j.Element;
0121: import org.dom4j.io.SAXReader;
0122: import org.xml.sax.InputSource;
0123:
0124: import com.ivata.mask.field.Field;
0125: import com.ivata.mask.field.FieldImpl;
0126: import com.ivata.mask.field.FieldValueConvertor;
0127: import com.ivata.mask.field.FieldValueConvertorFactory;
0128: import com.ivata.mask.filter.Filter;
0129: import com.ivata.mask.filter.FilterImpl;
0130: import com.ivata.mask.group.Group;
0131: import com.ivata.mask.group.GroupImpl;
0132: import com.ivata.mask.util.StringHandling;
0133: import com.ivata.mask.util.SystemException;
0134:
0135: /**
0136: * <p>
0137: * This factory class is at the heart of ivata masks. Use it to read in a
0138: * configuration file (in XML), and then access groups of fields via their
0139: * unique identifiers.
0140: * </p>
0141: *
0142: * <p>
0143: * It is called <code>DefaultMaskFactory</code> because the <strong>ivata
0144: * masks </strong> system actually never refers to this class directly - it uses
0145: * the interface {@link MaskFactory}, meaning you could create your own factory
0146: * implementation, if you want to.
0147: * </p>
0148: *
0149: * @author Colin MacLeod
0150: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
0151: * @since ivata masks 0.1 (2004-02-26)
0152: */
0153: public final class DefaultMaskFactory implements MaskFactory,
0154: Serializable {
0155: /**
0156: * Logger for this class.
0157: */
0158: private static final Logger logger = Logger
0159: .getLogger(DefaultMaskFactory.class);
0160:
0161: /**
0162: * Serialization version (for <code>Serializable</code> interface).
0163: */
0164: private static final long serialVersionUID = 1L;
0165: /**
0166: * <p>
0167: * The name of the default mask/screen used for user input.
0168: * </p>
0169: */
0170: private String defaultInputMask;
0171: /**
0172: * <p>
0173: * The name of the default mask/screen used to list.
0174: * </p>
0175: */
0176: private String defaultListMask;
0177: /**
0178: * Used to create convertors to convert field values in the filters.
0179: */
0180: private FieldValueConvertorFactory fieldValueConvertorFactory;
0181: /**
0182: * Mapping of all config groups, mapped by identifier.
0183: */
0184: private Map groups;
0185:
0186: /**
0187: * <p>
0188: * Default constructor. Initializes the mask factory with
0189: * "inputMask" as the default input mask, and
0190: * "inputMask" as the default list mask.
0191: * </p>
0192: * @param fieldValueConvertorFactoryParam creates convertors to convert
0193: * field values in the filters.
0194: */
0195: public DefaultMaskFactory(
0196: final FieldValueConvertorFactory fieldValueConvertorFactoryParam) {
0197: this ("inputMask", "listMask", fieldValueConvertorFactoryParam);
0198: }
0199:
0200: /**
0201: * <p>
0202: * Construct an instance of the factory with the default mask/screens
0203: * provided.
0204: * </p>
0205: *
0206: * @param defaultInputMaskParam
0207: * The name of the default mask/screen used for user input.
0208: * @param defaultListMaskParam
0209: * The name of the default mask/screen used to list.
0210: * @param fieldValueConvertorFactoryParam creates convertors to convert
0211: * field values in the filters.
0212: */
0213: public DefaultMaskFactory(
0214: final String defaultInputMaskParam,
0215: final String defaultListMaskParam,
0216: final FieldValueConvertorFactory fieldValueConvertorFactoryParam) {
0217: this .defaultInputMask = defaultInputMaskParam;
0218: this .defaultListMask = defaultListMaskParam;
0219: this .fieldValueConvertorFactory = fieldValueConvertorFactoryParam;
0220: }
0221:
0222: /**
0223: * <p>
0224: * Go thro' all the properties of the value object class and add fields for
0225: * those properties which were not explicitly defined in the configuration
0226: * file.
0227: * </p>
0228: *
0229: * @param mask
0230: * Mask for which to add all the default fields
0231: * @param parentField
0232: * If this mask applies to a field within another mask, (known as
0233: * a submask) this is the field to which it applies, otherwise
0234: * <code>null</code>.
0235: */
0236: private void addDefaultFields(final MaskImpl mask,
0237: final Field parentField) {
0238: if (logger.isDebugEnabled()) {
0239: logger.debug("addDefaultFields(MaskImpl mask = " + mask
0240: + ", Field parentField = " + parentField
0241: + ") - start");
0242: }
0243:
0244: Class dOClass = mask.getDOClass();
0245: PropertyDescriptor[] descriptors = PropertyUtils
0246: .getPropertyDescriptors(dOClass);
0247: for (int i = 0; i < descriptors.length; i++) {
0248: PropertyDescriptor descriptor = descriptors[i];
0249: String fieldName = descriptor.getName();
0250: // ignore getClass() from Object
0251: if ("class".equals(fieldName)) {
0252: continue;
0253: }
0254: StringBuffer combinedName = new StringBuffer();
0255: if (parentField != null) {
0256: combinedName.append(parentField.getPath());
0257: combinedName.append(".");
0258: }
0259: combinedName.append(fieldName);
0260: if (mask.getField(combinedName.toString()) == null) {
0261: FieldImpl field = new FieldImpl(parentField, mask
0262: .getField(fieldName), this );
0263: field.setName(fieldName);
0264: Class fieldClass = descriptor.getPropertyType();
0265: // guess the field type
0266: if (Timestamp.class.isAssignableFrom(fieldClass)) {
0267: field.setType(Field.TYPE_TIMESTAMP);
0268: } else if (Date.class.isAssignableFrom(fieldClass)) {
0269: field.setType(Field.TYPE_DATE);
0270: } else if (BigDecimal.class
0271: .isAssignableFrom(fieldClass)
0272: || Double.class.isAssignableFrom(fieldClass)) {
0273: field.setType(Field.TYPE_AMOUNT);
0274: } else if (Integer.class.isAssignableFrom(fieldClass)) {
0275: field.setType(Field.TYPE_NUMBER);
0276: } else if (String.class.isAssignableFrom(fieldClass)) {
0277: field.setType(Field.TYPE_STRING);
0278: } else {
0279: field.setType(null);
0280: }
0281: mask.addField(field);
0282: }
0283: }
0284:
0285: if (logger.isDebugEnabled()) {
0286: logger.debug("addDefaultFields(MaskImpl, Field) - end");
0287: }
0288: }
0289:
0290: /**
0291: * Extract a field from a dom4j element.
0292: *
0293: * @param element
0294: * dom4j element which represents a field.
0295: * @param group
0296: * group which will contain this field.
0297: * @return field New field represented by the element provided.
0298: * TODO: replace NullPointerException thrown here with a mask configuration
0299: * exception.
0300: */
0301: private Field extractField(final Group group, final Element element) {
0302: if (logger.isDebugEnabled()) {
0303: logger.debug("extractField(Group group = " + group
0304: + ", Element element = " + element + ") - start");
0305: }
0306:
0307: // the field can either extend another one in this group explicitly,
0308: // or a field in a parent group implicitly
0309: String extendsField = element.attributeValue("extends");
0310: String name = element.attributeValue("name");
0311: if (logger.isDebugEnabled()) {
0312: logger.debug("Extracting field '" + name + "'");
0313: }
0314: if (name == null) {
0315: throw new NullPointerException(
0316: "ERROR in mask configuration: "
0317: + "mandatory name attribute null for field.");
0318: }
0319: Field extendedField;
0320: if (extendsField == null) {
0321: // look for a field of the same id which already exists in this
0322: // group or a parent
0323: extendedField = group.getField(name);
0324: } else {
0325: // look for a field with the extended name
0326: extendedField = group.getField(extendsField);
0327: // in this case, it is an error if the field is <code>null</code>
0328: if (extendedField == null) {
0329: throw new NullPointerException(
0330: "ERROR in mask configuration: " + "field '"
0331: + name + "' extends unknown field '"
0332: + extendsField + "'");
0333: }
0334: }
0335: FieldImpl field = new FieldImpl(null, extendedField, this );
0336: field.setName(name);
0337: String type = element.attributeValue("type");
0338: field.setType(type);
0339:
0340: String displayOnly = element.attributeValue("displayOnly");
0341: field.setDisplayOnly("true".equalsIgnoreCase(displayOnly));
0342: String hidden = element.attributeValue("hidden");
0343: field.setHidden("true".equalsIgnoreCase(hidden));
0344: String mandatory = element.attributeValue("mandatory");
0345: field.setMandatory("true".equalsIgnoreCase(mandatory));
0346: String oneToOne = element.attributeValue("oneToOne");
0347: field.setOneToOne("true".equalsIgnoreCase(oneToOne));
0348: String defaultValue = element.attributeValue("default");
0349: field.setDefaultValue(defaultValue);
0350: String labelKey = element.attributeValue("labelKey");
0351: field.setLabelKey(labelKey);
0352: String className = element.attributeValue("class");
0353: // if a class name is set, find and set the dependent value object
0354: if (className != null) {
0355: try {
0356: Class dOClass = Class.forName(className);
0357: field.setDOClass(dOClass);
0358: } catch (ClassNotFoundException e) {
0359: logger.error("extractField(Group, Element)", e);
0360:
0361: throw new RuntimeException("ERROR (" + e.getClass()
0362: + ") cannot locate class: " + className + ": "
0363: + e.getMessage());
0364: }
0365: }
0366: List choiceList = element.elements("choice");
0367: Properties choiceProperties = field.getChoiceProperties();
0368: if (choiceList.size() > 0) {
0369: choiceProperties = new Properties();
0370: List choicePropertyKeys = new ArrayList();
0371: for (Iterator iterator = choiceList.iterator(); iterator
0372: .hasNext();) {
0373: Element choice = (Element) iterator.next();
0374: String key = choice.attributeValue("value");
0375: String text = choice.getText();
0376: if (key == null) {
0377: key = text;
0378: }
0379: choiceProperties.setProperty(key, text);
0380: choicePropertyKeys.add(key);
0381: }
0382: field.setChoiceProperties(choiceProperties);
0383: field.setChoicePropertyKeys(choicePropertyKeys);
0384: }
0385: // for select or radio types, there must either be a value object class
0386: if (("select".equals(type) || "radio".equals(type))
0387: && (className == null) && (choiceProperties == null)) {
0388: throw new RuntimeException(
0389: "ERROR in mask configuration: "
0390: + "you must specify either choices or value object class for "
0391: + "field " + type + ", name '" + name + "'");
0392: }
0393:
0394: if (logger.isDebugEnabled()) {
0395: logger
0396: .debug("extractField(Group, Element) - end - return value = "
0397: + field);
0398: }
0399: return field;
0400: }
0401:
0402: /**
0403: * Extract a single filter from the group provided.
0404: *
0405: * @param groupParam parent group surrounding the filter.
0406: * @param element the document element from which to extract the filter.
0407: * @return the extracted filter, represented by the XML in the
0408: * <code>element</code>.
0409: */
0410: private Filter extractFilter(final Group groupParam,
0411: final Element element) {
0412: if (logger.isDebugEnabled()) {
0413: logger.debug("extractFilter(Group groupParam = "
0414: + groupParam + ", Element element = " + element
0415: + ") - start");
0416: }
0417:
0418: String propertyName = element.attributeValue("propertyName");
0419: String stringValue = element.attributeValue("value");
0420: String className = element.attributeValue("propertyClass");
0421: if (logger.isDebugEnabled()) {
0422: logger.debug("Extracting filter on property '"
0423: + propertyName + "' for value '" + stringValue
0424: + "'");
0425: }
0426: Class propertyClass;
0427: try {
0428: propertyClass = Class.forName(className);
0429: } catch (ClassNotFoundException e) {
0430: logger.error("extractFilter(Group, Element)", e);
0431:
0432: throw new RuntimeException(e);
0433: }
0434: FieldValueConvertor convertor;
0435: try {
0436: convertor = fieldValueConvertorFactory
0437: .getFieldValueConvertorForClass(propertyClass);
0438: } catch (SystemException e) {
0439: logger.error("extractFilter(Group, Element)", e);
0440:
0441: throw new RuntimeException(e);
0442: }
0443: Object value = convertor.convertFromString(propertyClass,
0444: stringValue);
0445: Filter returnFilter = new FilterImpl(propertyName,
0446: propertyClass, value);
0447: if (logger.isDebugEnabled()) {
0448: logger.debug("extractFilter - end - return value = "
0449: + returnFilter);
0450: }
0451: return returnFilter;
0452: }
0453:
0454: /**
0455: * Extracts a single group from the element provided.
0456: *
0457: * @param element
0458: * The document element from which to extract the group.
0459: * @return New group represented by the XML in <code>element</code>.
0460: * TODO: replace NullPointerException thrown here with a mask configuration
0461: * exception.
0462: */
0463: private Group extractGroup(final Element element) {
0464: if (logger.isDebugEnabled()) {
0465: logger.debug("extractGroup(Element element = " + element
0466: + ") - start");
0467: }
0468:
0469: // the group can extend another one explicitly
0470: String extendsGroup = element.attributeValue("extends");
0471: String name = element.attributeValue("name");
0472: if (logger.isDebugEnabled()) {
0473: logger.debug("Extracting group '" + name + "'");
0474: }
0475: if (StringHandling.isNullOrEmpty(name)) {
0476: throw new NullPointerException(
0477: "ERROR in mask configuration: "
0478: + "mandatory name attribute null for group.");
0479: }
0480: Group parent = null;
0481: if (extendsGroup != null) {
0482: parent = (Group) groups.get(extendsGroup);
0483: if (parent == null) {
0484: throw new NullPointerException(
0485: "ERROR in mask configuration: " + "group '"
0486: + name + "' extends unknown group '"
0487: + extendsGroup + "'");
0488: }
0489: }
0490: GroupImpl group = new GroupImpl(name, parent);
0491: // groups contain field definitions and masks - first the fields
0492: extractGroupFields(element, group);
0493: // now extract the masks
0494: for (Iterator i = element.elementIterator("mask"); i.hasNext();) {
0495: Element maskElement = (Element) i.next();
0496: Mask mask = extractMask(group, maskElement);
0497: // if the class is unavailable, don't create the mask
0498: if (mask != null) {
0499: String maskId = getMaskId(mask.getDOClass().getName(),
0500: mask.getName());
0501: groups.put(maskId, mask);
0502: }
0503: }
0504:
0505: if (logger.isDebugEnabled()) {
0506: logger
0507: .debug("extractGroup(Element) - end - return value = "
0508: + group);
0509: }
0510: return group;
0511: }
0512:
0513: /**
0514: * This section is used by both <code>extractMask</code> and
0515: * <code>extractGroup</code>.
0516: *
0517: * @param element
0518: * dom4j element to extract the information from.
0519: * @param group
0520: * group or mask to set the information into.
0521: */
0522: private void extractGroupFields(final Element element,
0523: final GroupImpl group) {
0524: if (logger.isDebugEnabled()) {
0525: logger.debug("extractGroupFields(Element element = "
0526: + element + ", GroupImpl group = " + group
0527: + ") - start");
0528: }
0529:
0530: for (Iterator i = element.elementIterator("field"); i.hasNext();) {
0531: Element fieldElement = (Element) i.next();
0532: Field field = extractField(group, fieldElement);
0533: group.addField(field);
0534: }
0535: // find excluded fields
0536: Element exclude = element.element("exclude");
0537: if (exclude != null) {
0538: for (Iterator iter = exclude.elementIterator("fieldName"); iter
0539: .hasNext();) {
0540: Element fieldNameElement = (Element) iter.next();
0541: group.addExcludedFieldName(fieldNameElement
0542: .getTextTrim());
0543: }
0544: }
0545: // find included fields
0546: Element include = element.element("include");
0547: if (include != null) {
0548: for (Iterator iter = include.elementIterator("fieldName"); iter
0549: .hasNext();) {
0550: Element fieldNameElement = (Element) iter.next();
0551: group.addIncludedFieldName(fieldNameElement
0552: .getTextTrim());
0553: }
0554: }
0555: // if this group is marked display only, note that
0556: String displayOnly = element.attributeValue("displayOnly");
0557: if (displayOnly != null) {
0558: group.setDisplayOnly("true".equals(displayOnly));
0559: }
0560: // find fields at start and end
0561: Element first = element.element("first");
0562: if (first != null) {
0563: for (Iterator iter = first.elementIterator("fieldName"); iter
0564: .hasNext();) {
0565: Element fieldNameElement = (Element) iter.next();
0566: group.addFirstFieldName(fieldNameElement.getText());
0567: }
0568: }
0569: Element last = element.element("last");
0570: if (last != null) {
0571: for (Iterator iter = last.elementIterator("fieldName"); iter
0572: .hasNext();) {
0573: Element fieldNameElement = (Element) iter.next();
0574: group.addLastFieldName(fieldNameElement.getText());
0575: }
0576: }
0577: // filters
0578: for (Iterator i = element.elementIterator("filter"); i
0579: .hasNext();) {
0580: Element filterElement = (Element) i.next();
0581: Filter filter = extractFilter(group, filterElement);
0582: group.addFilter(filter);
0583: }
0584:
0585: if (logger.isDebugEnabled()) {
0586: logger
0587: .debug("extractGroupFields(Element, GroupImpl) - end");
0588: }
0589: }
0590:
0591: /**
0592: * Extracts a single mask from the mask element provided.
0593: *
0594: * @param group
0595: * The parent group surrounding the mask.
0596: * @param element
0597: * The document element from which to extract the mask.
0598: * @return New mask represented by the XML in <code>element</code>.
0599: * TODO: replace NullPointerException thrown here with a mask configuration
0600: * exception.
0601: */
0602: private Mask extractMask(final Group group, final Element element) {
0603: if (logger.isDebugEnabled()) {
0604: logger.debug("extractMask(Group group = " + group
0605: + ", Element element = " + element + ") - start");
0606: }
0607:
0608: String name = element.attributeValue("name");
0609: if (logger.isDebugEnabled()) {
0610: logger.debug("Extracting mask '" + name + "'");
0611: }
0612: String className = element.attributeValue("valueObject");
0613: // you must always supply a class name for a mask
0614: if (className == null) {
0615: throw new NullPointerException(
0616: "ERROR in mask configuration: "
0617: + "mandatory 'valueObject' attribute null for mask'"
0618: + name + "'.");
0619: }
0620: // now get the dependent value object class
0621: Class dOClass;
0622: try {
0623: dOClass = Class.forName(className);
0624: } catch (ClassNotFoundException e) {
0625: String message = "Cannot locate class specified in ivata masks: "
0626: + className;
0627: logger.error(message, e);
0628: throw new RuntimeException(message, e);
0629: }
0630: // the mask can extend another one explicitly, or a containing group
0631: // implicitly
0632: String extendsMask = element.attributeValue("extends");
0633: Group parent = null;
0634: if (extendsMask != null) {
0635: // first try extends as a reference to another mask..
0636: String id = getMaskId(className, extendsMask);
0637: parent = (Group) groups.get(id);
0638: // if there is no mask with this combination, look for a group with
0639: // the extended name
0640: if (parent == null) {
0641: parent = (Group) groups.get(extendsMask);
0642: if (parent == null) {
0643: throw new NullPointerException(
0644: "ERROR in mask configuration: mask '"
0645: + name + "' (value object '"
0646: + className
0647: + "') extends unknown mask/group '"
0648: + extendsMask + "'");
0649: }
0650: }
0651: } else {
0652: // if there is no explicit extends, it extends from the parent
0653: // group around it
0654: parent = group;
0655: }
0656: MaskImpl mask = new MaskImpl(dOClass, parent, name);
0657: // this section is shared with group
0658: extractGroupFields(element, mask);
0659: addDefaultFields(mask, null);
0660:
0661: // extract any include paths
0662: for (Iterator i = element.elementIterator("include"); i
0663: .hasNext();) {
0664: Element includeElement = (Element) i.next();
0665: String path = includeElement.attributeValue("path");
0666: if (StringHandling.isNullOrEmpty(path)) {
0667: path = includeElement.getTextTrim();
0668: }
0669: if (StringHandling.isNullOrEmpty(path)) {
0670: throw new NullPointerException(
0671: "ERROR in mask configuration: "
0672: + "you must specify either a path or body content "
0673: + "for all includes in mask '"
0674: + mask.getName() + "'.");
0675: }
0676: mask.addIncludePath(path);
0677: }
0678:
0679: if (logger.isDebugEnabled()) {
0680: logger
0681: .debug("extractMask(Group, Element) - end - return value = "
0682: + mask);
0683: }
0684: return mask;
0685: }
0686:
0687: /**
0688: * <p>
0689: * Get the name of the default mask/screen used for user input.
0690: * </p>
0691: *
0692: * @return name of the default mask/screen used for user input.
0693: * @see com.ivata.mask.MaskFactory#getDefaultInputMask()
0694: */
0695: public String getDefaultInputMask() {
0696: if (logger.isDebugEnabled()) {
0697: logger.debug("getDefaultInputMask() - start");
0698: }
0699:
0700: // by default just return the name "inputMask"
0701:
0702: if (logger.isDebugEnabled()) {
0703: logger
0704: .debug("getDefaultInputMask() - end - return value = "
0705: + defaultInputMask);
0706: }
0707: return defaultInputMask;
0708: }
0709:
0710: /**
0711: * <p>
0712: * Get the name of the default mask/screen used for lists.
0713: * </p>
0714: *
0715: * @return name of the default mask/screen used for lists.
0716: * @see com.ivata.mask.MaskFactory#getDefaultListMask()
0717: */
0718: public String getDefaultListMask() {
0719: if (logger.isDebugEnabled()) {
0720: logger.debug("getDefaultListMask() - start");
0721: }
0722:
0723: // by default just return the name "listMask"
0724:
0725: if (logger.isDebugEnabled()) {
0726: logger.debug("getDefaultListMask() - end - return value = "
0727: + defaultListMask);
0728: }
0729: return defaultListMask;
0730: }
0731:
0732: /**
0733: * <p>
0734: * Get a group definition referenced by its id.
0735: * </p>
0736: *
0737: * @param id
0738: * unique identifier of the group.
0739: * @return Group definition with the id provided, or <code>null</code> if
0740: * there is no such group.
0741: */
0742: public Group getGroup(final String id) {
0743: if (logger.isDebugEnabled()) {
0744: logger.debug("getGroup(String id = " + id + ") - start");
0745: }
0746:
0747: if (!isConfigured()) {
0748: throw new RuntimeException(
0749: "ERROR in MaskFactory: you must first read in configuration"
0750: + " by calling readConfiguration.");
0751: }
0752: Group returnGroup = (Group) groups.get(id);
0753: if (logger.isDebugEnabled()) {
0754: logger.debug("getGroup(String) - end - return value = "
0755: + returnGroup);
0756: }
0757: return returnGroup;
0758: }
0759:
0760: /**
0761: * This will return the <u>default input mask</u> for the class provided.
0762: * <copyDoc>Refer to {@link MaskFactory#getMask}.</copyDoc>
0763: *
0764: * @param valueObjectClassParam
0765: * <copyDoc>Refer to {@link MaskFactory#getMask}.</copyDoc>
0766: * @return <copyDoc>Refer to {@link MaskFactory#getMask}.</copyDoc>
0767: */
0768: public Mask getMask(final Class valueObjectClassParam) {
0769: if (logger.isDebugEnabled()) {
0770: logger.debug("getMask(Class valueObjectClassParam = "
0771: + valueObjectClassParam + ") - start");
0772: }
0773:
0774: Mask returnMask = getMask(valueObjectClassParam,
0775: getDefaultInputMask());
0776: if (logger.isDebugEnabled()) {
0777: logger.debug("getMask(Class) - end - return value = "
0778: + returnMask);
0779: }
0780: return returnMask;
0781: }
0782:
0783: /**
0784: * <p>
0785: * Get a mask, identified by its class and name.
0786: * </p>
0787: *
0788: * @param valueObjectClass
0789: * class of value object for the mask to be returned.
0790: * @param name
0791: * optional parameter defining multiple masks for the same value
0792: * object. May be <code>null</code>.
0793: * @return Mask definition with the id provided, or <code>null</code> if
0794: * there is no such mask.
0795: */
0796: public Mask getMask(final Class valueObjectClass, final String name) {
0797: if (logger.isDebugEnabled()) {
0798: logger.debug("getMask(Class valueObjectClass = "
0799: + valueObjectClass + ", String name = " + name
0800: + ") - start");
0801: }
0802:
0803: Mask returnMask = getMask(null, valueObjectClass, name);
0804: if (logger.isDebugEnabled()) {
0805: logger
0806: .debug("getMask(Class, String) - end - return value = "
0807: + returnMask);
0808: }
0809: return returnMask;
0810: }
0811:
0812: /**
0813: * <p>
0814: * This will return the <u>default input mask</u> for the class provided
0815: * of the sub-classed field.
0816: * </p>
0817: *
0818: * {@inheritDoc}
0819: *
0820: * @param parentField {@inheritDoc}
0821: * @param valueObjectClassParam {@inheritDoc}
0822: * @return {@inheritDoc}
0823: */
0824: public Mask getMask(final Field parentField,
0825: final Class valueObjectClassParam) {
0826: if (logger.isDebugEnabled()) {
0827: logger.debug("getMask(Field parentField = " + parentField
0828: + ", Class valueObjectClassParam = "
0829: + valueObjectClassParam + ") - start");
0830: }
0831:
0832: Mask returnMask = getMask(parentField, valueObjectClassParam,
0833: getDefaultInputMask());
0834: if (logger.isDebugEnabled()) {
0835: logger
0836: .debug("getMask(Field, Class) - end - return value = "
0837: + returnMask);
0838: }
0839: return returnMask;
0840: }
0841:
0842: /**
0843: * <p>
0844: * Get a mask, identified by its class and name.
0845: * </p>
0846: *
0847: * @param parentField
0848: * If this mask applies to a field within another mask, (known as
0849: * a submask) this is the field to which it applies, otherwise
0850: * use the other <code>getMask</code> method.
0851: * @param valueObjectClass
0852: * class of value object for the mask to be returned.
0853: * @param nameParam
0854: * describes this mask uniquely within the value object. (You
0855: * can have more than one mask for each value object.)
0856: * @return Mask definition with the id provided, or <code>null</code> if
0857: * there is no such mask.
0858: * TODO: replace NullPointerException thrown here with a mask configuration
0859: * exception.
0860: */
0861: public Mask getMask(final Field parentField,
0862: final Class valueObjectClass, final String nameParam) {
0863: if (logger.isDebugEnabled()) {
0864: logger
0865: .debug("getMask(Field parentField = " + parentField
0866: + ", Class valueObjectClass = "
0867: + valueObjectClass
0868: + ", String nameParam = " + nameParam
0869: + ") - start");
0870: }
0871:
0872: StringBuffer combinedName = new StringBuffer();
0873: if (parentField != null) {
0874: combinedName.append(parentField.getPath());
0875: combinedName.append(".");
0876: }
0877: combinedName.append(valueObjectClass.getName());
0878: String id = getMaskId(combinedName.toString(), nameParam);
0879: Group group = getGroup(id);
0880: if (group == null) {
0881: // first see if there is a mask defined for this field. If so,
0882: // we'll extend this one to make the sublist mask
0883: String parentId = getMaskId(valueObjectClass.getName(),
0884: nameParam);
0885: Group parent = getGroup(parentId);
0886: // if there is no mask defined, create a default mask from the group
0887: if (parent == null) {
0888: parent = getGroup(nameParam);
0889: if (parent == null) {
0890: throw new NullPointerException(
0891: "ERROR: no appropriate mask or group called '"
0892: + nameParam + "' for class '"
0893: + valueObjectClass.getName() + "'.");
0894: }
0895: }
0896:
0897: // if not, create a default mask in the parent group
0898: MaskImpl defaultMask = new MaskImpl(valueObjectClass,
0899: parent, nameParam);
0900: addDefaultFields(defaultMask, parentField);
0901: groups.put(combinedName.toString(), defaultMask);
0902:
0903: if (logger.isDebugEnabled()) {
0904: logger.debug("getMask - end - return value = "
0905: + defaultMask);
0906: }
0907: return defaultMask;
0908: }
0909: if (!(group instanceof Mask)) {
0910: throw new RuntimeException("ERROR: the group '" + id
0911: + "' does not represent a mask.");
0912: }
0913: Mask returnMask = (Mask) group;
0914: if (logger.isDebugEnabled()) {
0915: logger
0916: .debug("getMask - end - return value = "
0917: + returnMask);
0918: }
0919: return returnMask;
0920: }
0921:
0922: /**
0923: * <p>
0924: * Create a unique identifier for the mask, based on the class and name.
0925: * For groups, we use the name alone as unique.
0926: * </p>
0927: *
0928: * @param className
0929: * name of the class of value object for the mask to be returned.
0930: * @param name
0931: * describes this mask uniquely within the value object. (You
0932: * can have more than one mask for each value object.)
0933: * @return Unique identifier for the mask based on class name and mask name.
0934: */
0935: private String getMaskId(final String className, final String name) {
0936: if (logger.isDebugEnabled()) {
0937: logger.debug("getMaskId(String className = " + className
0938: + ", String name = " + name + ") - start");
0939: }
0940:
0941: if (name == null) {
0942: if (logger.isDebugEnabled()) {
0943: logger.debug("getMaskId - end - return value = "
0944: + className);
0945: }
0946: return className;
0947: } else {
0948: String returnString = className + "_" + name;
0949: if (logger.isDebugEnabled()) {
0950: logger.debug("getMaskId - end - return value = "
0951: + returnString);
0952: }
0953: return returnString;
0954: }
0955: }
0956:
0957: /**
0958: * <p>
0959: * Discover whether or not this object has been configured.
0960: * </p>
0961: *
0962: * @return <code>true</code> if the object has been configured, otherwise
0963: * <code>false</code>.
0964: * @see com.ivata.mask.MaskFactory#isConfigured()
0965: */
0966: public boolean isConfigured() {
0967: if (logger.isDebugEnabled()) {
0968: logger.debug("isConfigured() - start");
0969: }
0970:
0971: // if there are no groups, assume it wasn't configured
0972: boolean returnboolean = groups != null;
0973: if (logger.isDebugEnabled()) {
0974: logger.debug("isConfigured() - end - return value = "
0975: + returnboolean);
0976: }
0977: return returnboolean;
0978: }
0979:
0980: /**
0981: * <p>
0982: * Read in the configuration represented by the <strong>dom4j </strong>
0983: * document provided.
0984: * </p>
0985: *
0986: * @param document
0987: * <strong>dom4j </strong> document to read configuration from.
0988: * @throws IOException
0989: * If there is any problem reading from the stream provided.
0990: */
0991: private synchronized void readConfiguration(final Document document)
0992: throws IOException {
0993: if (logger.isDebugEnabled()) {
0994: logger.debug("readConfiguration(Document document = "
0995: + document + ") - start");
0996: }
0997:
0998: Element root = document.getRootElement();
0999: groups = new HashMap();
1000: for (Iterator i = root.elementIterator("group"); i.hasNext();) {
1001: Element groupElement = (Element) i.next();
1002: Group group = extractGroup(groupElement);
1003: groups.put(group.getName(), group);
1004: }
1005:
1006: if (logger.isDebugEnabled()) {
1007: logger.debug("readConfiguration(Document) - end");
1008: }
1009: }
1010:
1011: /**
1012: * Get the configuration represented by the <i>dom4j </i> document provided.
1013: *
1014: * @param inputStream
1015: * The input stream to read the XML from.
1016: * @throws IOException
1017: * If there is any problem reading from the stream provided.
1018: */
1019: public void readConfiguration(final InputStream inputStream)
1020: throws IOException {
1021: if (logger.isDebugEnabled()) {
1022: logger.debug("readConfiguration(InputStream inputStream = "
1023: + inputStream + ") - start");
1024: }
1025:
1026: SAXReader reader = new SAXReader();
1027: Document document;
1028: try {
1029: InputSource is = new InputSource();
1030: is.setByteStream(inputStream);
1031: document = reader.read(is);
1032: } catch (DocumentException e) {
1033: logger.error("readConfiguration(InputStream)", e);
1034:
1035: e.printStackTrace();
1036: throw new IOException("ERROR in MaskConfigurationFactory: "
1037: + e.getMessage());
1038: }
1039: readConfiguration(document);
1040:
1041: if (logger.isDebugEnabled()) {
1042: logger.debug("readConfiguration(InputStream) - end");
1043: }
1044: }
1045: }
|