0001: /*
0002: * Copyright 2005-2007 The Kuali Foundation.
0003: *
0004: * Licensed under the Educational Community License, Version 1.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.opensource.org/licenses/ecl1.php
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.kuali.core.util;
0017:
0018: import java.lang.reflect.InvocationTargetException;
0019: import java.security.GeneralSecurityException;
0020: import java.util.ArrayList;
0021: import java.util.HashMap;
0022: import java.util.Iterator;
0023: import java.util.List;
0024: import java.util.Map;
0025:
0026: import org.apache.commons.beanutils.PropertyUtils;
0027: import org.apache.commons.lang.StringUtils;
0028: import org.kuali.RiceConstants;
0029: import org.kuali.core.authorization.FieldAuthorization;
0030: import org.kuali.core.bo.BusinessObject;
0031: import org.kuali.core.datadictionary.MaintainableCollectionDefinition;
0032: import org.kuali.core.datadictionary.control.ApcSelectControlDefinition;
0033: import org.kuali.core.datadictionary.control.ControlDefinition;
0034: import org.kuali.core.datadictionary.control.CurrencyControlDefinition;
0035: import org.kuali.core.datadictionary.control.KualiUserControlDefinition;
0036: import org.kuali.core.document.authorization.MaintenanceDocumentAuthorizations;
0037: import org.kuali.core.exceptions.UnknownBusinessClassAttributeException;
0038: import org.kuali.core.inquiry.Inquirable;
0039: import org.kuali.core.inquiry.KualiInquirableImpl;
0040: import org.kuali.core.lookup.LookupUtils;
0041: import org.kuali.core.lookup.keyvalues.ApcValuesFinder;
0042: import org.kuali.core.lookup.keyvalues.IndicatorValuesFinder;
0043: import org.kuali.core.lookup.keyvalues.KeyValuesFinder;
0044: import org.kuali.core.lookup.valueFinder.ValueFinder;
0045: import org.kuali.core.service.BusinessObjectDictionaryService;
0046: import org.kuali.core.service.BusinessObjectMetaDataService;
0047: import org.kuali.core.service.DataDictionaryService;
0048: import org.kuali.core.service.EncryptionService;
0049: import org.kuali.core.service.KualiConfigurationService;
0050: import org.kuali.core.web.format.FormatException;
0051: import org.kuali.core.web.format.Formatter;
0052: import org.kuali.core.web.ui.Field;
0053: import org.kuali.core.web.ui.Row;
0054: import org.kuali.core.web.ui.Section;
0055: import org.kuali.rice.KNSServiceLocator;
0056:
0057: /**
0058: * This class is used to build Field objects from underlying data dictionary and general utility methods for handling fields.
0059: */
0060: public class FieldUtils {
0061: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
0062: .getLogger(FieldUtils.class);
0063: private static DataDictionaryService dictionaryService = KNSServiceLocator
0064: .getDataDictionaryService();
0065: private static BusinessObjectMetaDataService businessObjectMetaDataService = KNSServiceLocator
0066: .getBusinessObjectMetaDataService();
0067: private static BusinessObjectDictionaryService businessObjectDictionaryService = KNSServiceLocator
0068: .getBusinessObjectDictionaryService();
0069: private static KualiConfigurationService kualiConfigurationService = KNSServiceLocator
0070: .getKualiConfigurationService();
0071:
0072: public static void setInquiryURL(Field field, BusinessObject bo,
0073: String propertyName) {
0074: String inquiryUrl = "";
0075:
0076: Boolean b = businessObjectDictionaryService
0077: .noInquiryFieldInquiry(bo.getClass(), propertyName);
0078: if (b == null || !b.booleanValue()) {
0079: Class<Inquirable> inquirableClass = businessObjectDictionaryService
0080: .getInquirableClass(bo.getClass());
0081: Boolean b2 = businessObjectDictionaryService
0082: .forceLookupResultFieldInquiry(bo.getClass(),
0083: propertyName);
0084: Inquirable inq = null;
0085: try {
0086: if (inquirableClass != null) {
0087: inq = inquirableClass.newInstance();
0088: } else {
0089: inq = KNSServiceLocator.getKualiInquirable();
0090: if (LOG.isDebugEnabled()) {
0091: LOG.debug("Default Inquirable Class: "
0092: + inq.getClass());
0093: }
0094: }
0095:
0096: inquiryUrl = inq.getInquiryUrl(bo, propertyName,
0097: null == b2 ? false : b2.booleanValue());
0098:
0099: } catch (Exception ex) {
0100: LOG
0101: .error(
0102: "unable to create inquirable to get inquiry URL",
0103: ex);
0104: }
0105: }
0106:
0107: field.setInquiryURL(inquiryUrl);
0108: }
0109:
0110: /**
0111: * Builds up a Field object based on the propertyName and business object class.
0112: *
0113: * @param propertyName
0114: * @return Field
0115: */
0116: public static Field getPropertyField(Class businessObjectClass,
0117: String attributeName, boolean translateCheckboxes) {
0118: Field field = new Field();
0119: field.setPropertyName(attributeName);
0120: field.setFieldLabel(dictionaryService.getAttributeLabel(
0121: businessObjectClass, attributeName));
0122:
0123: // get control type for ui, depending on type set other field properties
0124: ControlDefinition control = dictionaryService
0125: .getAttributeControlDefinition(businessObjectClass,
0126: attributeName);
0127: String fieldType = Field.TEXT;
0128:
0129: if (control != null) {
0130: if (control.isSelect()) {
0131: if (control.getScript() != null
0132: && control.getScript().length() > 0) {
0133: fieldType = Field.DROPDOWN_SCRIPT;
0134: field.setScript(control.getScript());
0135: } else {
0136: fieldType = Field.DROPDOWN;
0137: }
0138: }
0139:
0140: if (control.isApcSelect()) {
0141: fieldType = Field.DROPDOWN_APC;
0142: }
0143:
0144: if (control.isCheckbox()) {
0145: fieldType = Field.CHECKBOX;
0146: }
0147:
0148: if (control.isRadio()) {
0149: fieldType = Field.RADIO;
0150: }
0151:
0152: if (control.isHidden()) {
0153: fieldType = Field.HIDDEN;
0154: }
0155:
0156: if (control.isKualiUser()) {
0157: fieldType = Field.KUALIUSER;
0158: KualiUserControlDefinition kualiUserControl = (KualiUserControlDefinition) control;
0159: field.setUniversalIdAttributeName(kualiUserControl
0160: .getUniversalIdAttributeName());
0161: field.setUserIdAttributeName(kualiUserControl
0162: .getUserIdAttributeName());
0163: field.setPersonNameAttributeName(kualiUserControl
0164: .getPersonNameAttributeName());
0165: }
0166:
0167: if (control.isWorkflowWorkgroup()) {
0168: fieldType = Field.WORKFLOW_WORKGROUP;
0169: }
0170:
0171: if (control.isTextarea()) {
0172: fieldType = Field.TEXT_AREA;
0173: }
0174:
0175: if (control.isLookupHidden()) {
0176: fieldType = Field.LOOKUP_HIDDEN;
0177: }
0178:
0179: if (control.isLookupReadonly()) {
0180: fieldType = Field.LOOKUP_READONLY;
0181: }
0182:
0183: if (control.isCurrency()) {
0184: fieldType = Field.CURRENCY;
0185: }
0186:
0187: if (Field.CURRENCY.equals(fieldType)
0188: && control instanceof CurrencyControlDefinition) {
0189: CurrencyControlDefinition currencyControl = (CurrencyControlDefinition) control;
0190: field.setStyleClass("amount");
0191: field.setSize(currencyControl.getSize());
0192: field.setFormattedMaxLength(currencyControl
0193: .getFormattedMaxLength());
0194: }
0195:
0196: // for text controls, set size attribute
0197: if (Field.TEXT.equals(fieldType)) {
0198: Integer size = control.getSize();
0199: if (size != null) {
0200: field.setSize(size.intValue());
0201: } else {
0202: field.setSize(30);
0203: }
0204: field.setDatePicker(control.isDatePicker());
0205:
0206: }
0207:
0208: if (Field.WORKFLOW_WORKGROUP.equals(fieldType)) {
0209: Integer size = control.getSize();
0210: if (size != null) {
0211: field.setSize(size.intValue());
0212: } else {
0213: field.setSize(30);
0214: }
0215: }
0216:
0217: // for text area controls, set rows and cols attributes
0218: if (Field.TEXT_AREA.equals(fieldType)) {
0219: Integer rows = control.getRows();
0220: if (rows != null) {
0221: field.setRows(rows.intValue());
0222: } else {
0223: field.setRows(3);
0224: }
0225:
0226: Integer cols = control.getCols();
0227: if (cols != null) {
0228: field.setCols(cols.intValue());
0229: } else {
0230: field.setCols(40);
0231: }
0232: }
0233:
0234: // for dropdown and radio, get instance of specified KeyValuesFinder and set field values
0235: if (Field.DROPDOWN.equals(fieldType)
0236: || Field.RADIO.equals(fieldType)
0237: || Field.DROPDOWN_SCRIPT.equals(fieldType)
0238: || Field.DROPDOWN_APC.equals(fieldType)) {
0239: Class keyFinderClassName = control
0240: .getValuesFinderClass();
0241:
0242: if (keyFinderClassName != null) {
0243: try {
0244: KeyValuesFinder finder = (KeyValuesFinder) keyFinderClassName
0245: .newInstance();
0246:
0247: if (finder != null) {
0248: if (finder instanceof ApcValuesFinder
0249: && control instanceof ApcSelectControlDefinition) {
0250: ((ApcValuesFinder) finder)
0251: .setParameterNamespace(((ApcSelectControlDefinition) control)
0252: .getParameterNamespace());
0253: ((ApcValuesFinder) finder)
0254: .setParameterDetailType(((ApcSelectControlDefinition) control)
0255: .getParameterDetailType());
0256: ((ApcValuesFinder) finder)
0257: .setParameterName(((ApcSelectControlDefinition) control)
0258: .getParameterName());
0259: }
0260: field.setFieldValidValues(finder
0261: .getKeyValues());
0262: }
0263: } catch (InstantiationException e) {
0264: LOG
0265: .error("Unable to get new instance of finder class: "
0266: + keyFinderClassName);
0267: throw new RuntimeException(
0268: "Unable to get new instance of finder class: "
0269: + keyFinderClassName);
0270: } catch (IllegalAccessException e) {
0271: LOG
0272: .error("Unable to get new instance of finder class: "
0273: + keyFinderClassName);
0274: throw new RuntimeException(
0275: "Unable to get new instance of finder class: "
0276: + keyFinderClassName);
0277: }
0278: }
0279: }
0280:
0281: if (Field.CHECKBOX.equals(fieldType) && translateCheckboxes) {
0282: fieldType = Field.RADIO;
0283: field.setFieldValidValues((new IndicatorValuesFinder())
0284: .getKeyValues());
0285: }
0286: }
0287:
0288: field.setFieldType(fieldType);
0289:
0290: Boolean fieldRequired = KNSServiceLocator
0291: .getBusinessObjectDictionaryService()
0292: .getLookupAttributeRequired(businessObjectClass,
0293: attributeName);
0294: if (fieldRequired != null) {
0295: field.setFieldRequired(fieldRequired.booleanValue());
0296: }
0297:
0298: Integer maxLength = dictionaryService.getAttributeMaxLength(
0299: businessObjectClass, attributeName);
0300: if (maxLength != null) {
0301: field.setMaxLength(maxLength.intValue());
0302: }
0303:
0304: Boolean upperCase = null;
0305: try {
0306: upperCase = dictionaryService.getAttributeForceUppercase(
0307: businessObjectClass, attributeName);
0308: } catch (UnknownBusinessClassAttributeException t) {
0309: boolean catchme = true;
0310: // throw t;
0311: }
0312: if (upperCase != null) {
0313: field.setUpperCase(upperCase.booleanValue());
0314: }
0315:
0316: Class formatterClass = dictionaryService.getAttributeFormatter(
0317: businessObjectClass, attributeName);
0318: if (formatterClass != null) {
0319: try {
0320: field.setFormatter((Formatter) formatterClass
0321: .newInstance());
0322: } catch (InstantiationException e) {
0323: LOG
0324: .error("Unable to get new instance of formatter class: "
0325: + formatterClass.getName());
0326: throw new RuntimeException(
0327: "Unable to get new instance of formatter class: "
0328: + formatterClass.getName());
0329: } catch (IllegalAccessException e) {
0330: LOG
0331: .error("Unable to get new instance of formatter class: "
0332: + formatterClass.getName());
0333: throw new RuntimeException(
0334: "Unable to get new instance of formatter class: "
0335: + formatterClass.getName());
0336: }
0337: }
0338:
0339: // set Field help properties
0340: field.setBusinessObjectClassName(businessObjectClass.getName());
0341: field.setFieldHelpName(attributeName);
0342: field
0343: .setFieldHelpSummary(dictionaryService
0344: .getAttributeSummary(businessObjectClass,
0345: attributeName));
0346:
0347: return field;
0348: }
0349:
0350: /**
0351: * Wraps each Field in the list into a Row.
0352: *
0353: * @param fields
0354: * @return List of Row objects
0355: */
0356: public static List wrapFields(List fields) {
0357: return wrapFields(fields, RiceConstants.DEFAULT_NUM_OF_COLUMNS);
0358: }
0359:
0360: /**
0361: * This method is to implement multiple columns where the numberOfColumns is obtained from data dictionary.
0362: *
0363: * @param fields
0364: * @param numberOfColumns
0365: * @return
0366: */
0367: public static List<Row> wrapFields(List<Field> fields,
0368: int numberOfColumns) {
0369:
0370: List<Row> rows = new ArrayList();
0371: List<Field> fieldOnlyList = new ArrayList();
0372: int fieldsPosition = 0;
0373: for (Field element : fields) {
0374: if (Field.SUB_SECTION_SEPARATOR.equals(element
0375: .getFieldType())) {
0376: fieldsPosition = createBlankSpace(fieldOnlyList, rows,
0377: numberOfColumns, fieldsPosition);
0378: List fieldList = new ArrayList();
0379: fieldList.add(element);
0380: rows.add(new Row(fieldList));
0381: } else {
0382: if (fieldsPosition < numberOfColumns) {
0383: fieldOnlyList.add(element);
0384: fieldsPosition++;
0385: } else {
0386: rows.add(new Row(new ArrayList(fieldOnlyList)));
0387: fieldOnlyList.clear();
0388: fieldOnlyList.add(element);
0389: fieldsPosition = 1;
0390: }
0391: }
0392: }
0393: createBlankSpace(fieldOnlyList, rows, numberOfColumns,
0394: fieldsPosition);
0395: return rows;
0396: }
0397:
0398: /**
0399: * This is a helper method to create and add a blank space to the fieldOnly List.
0400: *
0401: * @param fieldOnlyList
0402: * @param rows
0403: * @param numberOfColumns
0404: * @return fieldsPosition
0405: */
0406: private static int createBlankSpace(List<Field> fieldOnlyList,
0407: List<Row> rows, int numberOfColumns, int fieldsPosition) {
0408: int fieldOnlySize = fieldOnlyList.size();
0409: if (fieldOnlySize > 0) {
0410: for (int i = 0; i < (numberOfColumns - fieldOnlySize); i++) {
0411: Field empty = new Field();
0412: empty.setFieldType(Field.BLANK_SPACE);
0413: fieldOnlyList.add(empty);
0414: }
0415: rows.add(new Row(new ArrayList(fieldOnlyList)));
0416: fieldOnlyList.clear();
0417: fieldsPosition = 0;
0418: }
0419: return fieldsPosition;
0420: }
0421:
0422: /**
0423: * Wraps list of fields into a Field of type CONTAINER
0424: *
0425: * @param name name for the field
0426: * @param label label for the field
0427: * @param fields list of fields that should be contained in the container
0428: * @return Field of type CONTAINER
0429: */
0430: public static Field constructContainerField(String name,
0431: String label, List fields) {
0432: return constructContainerField(name, label, fields,
0433: RiceConstants.DEFAULT_NUM_OF_COLUMNS);
0434: }
0435:
0436: /**
0437: * Wraps list of fields into a Field of type CONTAINER and arrange them into multiple columns.
0438: *
0439: * @param name name for the field
0440: * @param label label for the field
0441: * @param fields list of fields that should be contained in the container
0442: * @param numberOfColumns the number of columns for each row that the fields should be arranged into
0443: * @return Field of type CONTAINER
0444: */
0445: public static Field constructContainerField(String name,
0446: String label, List fields, int numberOfColumns) {
0447: Field containerField = new Field();
0448: containerField.setPropertyName(name);
0449: containerField.setFieldLabel(label);
0450: containerField.setFieldType(Field.CONTAINER);
0451: containerField.setNumberOfColumnsForCollection(numberOfColumns);
0452:
0453: List rows = wrapFields(fields, numberOfColumns);
0454: containerField.setContainerRows(rows);
0455:
0456: return containerField;
0457: }
0458:
0459: /**
0460: * Uses reflection to get the property names of the business object, then checks for a matching field property name. If found,
0461: * takes the value of the business object property and populates the field value. Iterates through for all fields in the list.
0462: *
0463: * @param fields list of Field object to populate
0464: * @param bo business object to get field values from
0465: * @return List of fields with values populated from business object.
0466: */
0467: public static List populateFieldsFromBusinessObject(List fields,
0468: BusinessObject bo) {
0469: List populatedFields = new ArrayList();
0470:
0471: for (Iterator iter = fields.iterator(); iter.hasNext();) {
0472: Field element = (Field) iter.next();
0473: if (element.containsBOData()) {
0474: String propertyName = element.getPropertyName();
0475:
0476: // See: https://test.kuali.org/jira/browse/KULCOA-1185
0477: // Properties that could not possibly be set by the BusinessObject should be ignored.
0478: // (https://test.kuali.org/jira/browse/KULRNE-4354; this code was killing the src attribute of IMAGE_SUBMITs).
0479: if (isPropertyNested(propertyName)
0480: && !isObjectTreeNonNullAllTheWayDown(bo,
0481: propertyName)
0482: && ((!element.getFieldType().equals(
0483: Field.IMAGE_SUBMIT))
0484: && !(element.getFieldType()
0485: .equals(Field.CONTAINER)) && (!element
0486: .getFieldType().equals(
0487: Field.QUICKFINDER)))) {
0488: element.setPropertyValue(null);
0489: } else if (PropertyUtils.isReadable(bo, propertyName)) {
0490: Object obj = ObjectUtils.getNestedValue(bo, element
0491: .getPropertyName());
0492: if (obj != null) {
0493: element.setPropertyValue(obj);
0494: }
0495:
0496: // set encrypted & masked value if user does not have permission to see real value in UI
0497: if (element.isSecure()) {
0498: try {
0499: if (obj != null
0500: && obj
0501: .toString()
0502: .endsWith(
0503: EncryptionService.HASH_POST_PREFIX)) {
0504: element.setEncryptedValue(obj
0505: .toString());
0506: } else {
0507: element
0508: .setEncryptedValue(KNSServiceLocator
0509: .getEncryptionService()
0510: .encrypt(obj)
0511: + EncryptionService.ENCRYPTION_POST_PREFIX);
0512: }
0513: } catch (GeneralSecurityException e) {
0514: throw new RuntimeException(
0515: "Unable to encrypt secure field "
0516: + e.getMessage());
0517: }
0518: element.setDisplayMaskValue(element
0519: .getDisplayMask().maskValue(obj));
0520: }
0521: }
0522: }
0523: populatedFields.add(element);
0524: }
0525:
0526: return populatedFields;
0527: }
0528:
0529: /**
0530: * This method indicates whether or not propertyName refers to a nested attribute.
0531: *
0532: * @param propertyName
0533: * @return true if propertyName refers to a nested property (e.g. "x.y")
0534: */
0535: static private boolean isPropertyNested(String propertyName) {
0536: return -1 != propertyName.indexOf('.');
0537: }
0538:
0539: /**
0540: * This method verifies that all of the parent objects of propertyName are non-null.
0541: *
0542: * @param bo
0543: * @param propertyName
0544: * @return true if all parents are non-null, otherwise false
0545: */
0546:
0547: static private boolean isObjectTreeNonNullAllTheWayDown(
0548: BusinessObject bo, String propertyName) {
0549: String[] propertyParts = propertyName.split("\\.");
0550:
0551: StringBuffer property = new StringBuffer();
0552: for (int i = 0; i < propertyParts.length - 1; i++) {
0553:
0554: property.append((0 == property.length()) ? "" : ".")
0555: .append(propertyParts[i]);
0556: try {
0557: if (null == PropertyUtils.getNestedProperty(bo,
0558: property.toString())) {
0559: return false;
0560: }
0561: } catch (Throwable t) {
0562: LOG.debug(
0563: "Either getter or setter not specified for property \""
0564: + property.toString() + "\"", t);
0565: return false;
0566: }
0567: }
0568:
0569: return true;
0570:
0571: }
0572:
0573: /**
0574: * @param bo
0575: * @param propertyName
0576: * @return true if one (or more) of the intermediate objects in the given propertyName is null
0577: */
0578: private static boolean containsIntermediateNull(Object bo,
0579: String propertyName) {
0580: boolean containsNull = false;
0581:
0582: if (StringUtils.contains(propertyName, ".")) {
0583: String prefix = StringUtils.substringBefore(propertyName,
0584: ".");
0585: Object propertyValue = ObjectUtils.getPropertyValue(bo,
0586: prefix);
0587:
0588: if (propertyValue == null) {
0589: containsNull = true;
0590: } else {
0591: String suffix = StringUtils.substringAfter(
0592: propertyName, ".");
0593: containsNull = containsIntermediateNull(propertyValue,
0594: suffix);
0595: }
0596: }
0597:
0598: return containsNull;
0599: }
0600:
0601: /**
0602: * Uses reflection to get the property names of the business object, then checks for the property name as a key in the passed
0603: * map. If found, takes the value from the map and sets the business object property.
0604: *
0605: * @param bo
0606: * @param fieldValues
0607: * @return Cached Values from any formatting failures
0608: */
0609: public static Map populateBusinessObjectFromMap(BusinessObject bo,
0610: Map fieldValues) {
0611: return populateBusinessObjectFromMap(bo, fieldValues, "");
0612: }
0613:
0614: /**
0615: * Uses reflection to get the property names of the business object, then checks for the property name as a key in the passed
0616: * map. If found, takes the value from the map and sets the business object property.
0617: *
0618: * @param bo
0619: * @param fieldValues
0620: * @param propertyNamePrefix this value will be prepended to all property names in the returned unformattable values map
0621: * @return Cached Values from any formatting failures
0622: */
0623: public static Map populateBusinessObjectFromMap(BusinessObject bo,
0624: Map fieldValues, String propertyNamePrefix) {
0625: Map cachedValues = new HashMap();
0626: ErrorMap errorMap = GlobalVariables.getErrorMap();
0627:
0628: try {
0629: for (Iterator iter = fieldValues.keySet().iterator(); iter
0630: .hasNext();) {
0631: String propertyName = (String) iter.next();
0632:
0633: if (propertyName
0634: .endsWith(RiceConstants.CHECKBOX_PRESENT_ON_FORM_ANNOTATION)) {
0635: // since checkboxes do not post values when unchecked, this code detects whether a checkbox was unchecked, and
0636: // sets the value to false.
0637: if (StringUtils.isNotBlank((String) fieldValues
0638: .get(propertyName))) {
0639: String checkboxName = StringUtils
0640: .removeEnd(
0641: propertyName,
0642: RiceConstants.CHECKBOX_PRESENT_ON_FORM_ANNOTATION);
0643: String checkboxValue = (String) fieldValues
0644: .get(checkboxName);
0645: if (checkboxValue == null) {
0646: // didn't find a checkbox value, assume that it is unchecked
0647: if (PropertyUtils.isWriteable(bo,
0648: checkboxName)) {
0649: Class type = ObjectUtils
0650: .easyGetPropertyType(bo,
0651: checkboxName);
0652: if (type == Boolean.TYPE
0653: || type == Boolean.class) {
0654: // ASSUMPTION: unchecked means false
0655: ObjectUtils
0656: .setObjectProperty(bo,
0657: checkboxName, type,
0658: "false");
0659: }
0660: }
0661: }
0662: }
0663: // else, if not null, then it has a value, and we'll let the rest of the code handle it when the param is processed on
0664: // another iteration (may be before or after this iteration).
0665: } else if (PropertyUtils.isWriteable(bo, propertyName)
0666: && fieldValues.get(propertyName) != null) {
0667: // if the field propertyName is a valid property on the bo class
0668: Class type = ObjectUtils.easyGetPropertyType(bo,
0669: propertyName);
0670: try {
0671: ObjectUtils.setObjectProperty(bo, propertyName,
0672: type, fieldValues.get(propertyName));
0673: } catch (FormatException e) {
0674: cachedValues.put(propertyNamePrefix
0675: + propertyName, fieldValues
0676: .get(propertyName));
0677: errorMap.putError(propertyName,
0678: e.getErrorKey(), e.getErrorArgs());
0679: }
0680: }
0681: }
0682: } catch (IllegalAccessException e) {
0683: LOG.error("unable to populate business object"
0684: + e.getMessage());
0685: throw new RuntimeException(e.getMessage(), e);
0686: } catch (InvocationTargetException e) {
0687: LOG.error("unable to populate business object"
0688: + e.getMessage());
0689: throw new RuntimeException(e.getMessage(), e);
0690: } catch (NoSuchMethodException e) {
0691: LOG.error("unable to populate business object"
0692: + e.getMessage());
0693: throw new RuntimeException(e.getMessage(), e);
0694: }
0695:
0696: return cachedValues;
0697: }
0698:
0699: /**
0700: * Does prefixing and read only settings of a Field UI for display in a maintenance document.
0701: *
0702: * @param field - the Field object to be displayed
0703: * @param keyFieldNames - Primary key property names for the business object being maintained.
0704: * @param namePrefix - String to prefix Field names with.
0705: * @param maintenanceAction - The maintenance action requested.
0706: * @param readOnly - Indicates whether all fields should be read only.
0707: * @return Field
0708: */
0709: public static Field fixFieldForForm(Field field,
0710: List keyFieldNames, String namePrefix,
0711: String maintenanceAction, boolean readOnly,
0712: MaintenanceDocumentAuthorizations auths) {
0713: String propertyName = field.getPropertyName();
0714: // We only need to do the following processing if the field is not a sub section header
0715: if (field.containsBOData()) {
0716:
0717: // don't prefix submit fields, must start with dispatch parameter name
0718: if (!propertyName
0719: .startsWith(RiceConstants.DISPATCH_REQUEST_PARAMETER)) {
0720: // if the developer hasn't set a specific prefix use the one supplied
0721: if (field.getPropertyPrefix() == null
0722: || field.getPropertyPrefix().equals("")) {
0723: field.setPropertyName(namePrefix + propertyName);
0724: } else {
0725: field.setPropertyName(field.getPropertyPrefix()
0726: + "." + propertyName);
0727: }
0728: }
0729:
0730: if (readOnly) {
0731: field.setReadOnly(true);
0732: }
0733:
0734: // set keys read only for edit
0735: if (keyFieldNames.contains(propertyName)
0736: && RiceConstants.MAINTENANCE_EDIT_ACTION
0737: .equals(maintenanceAction)) {
0738: field.setReadOnly(true);
0739: field.setKeyField(true);
0740: }
0741:
0742: // apply any authorization restrictions to field availability on the UI
0743: applyAuthorization(field, auths);
0744:
0745: // if fieldConversions specified, prefix with new constant
0746: if (StringUtils.isNotBlank(field.getFieldConversions())) {
0747: String fieldConversions = field.getFieldConversions();
0748: String newFieldConversions = RiceConstants.EMPTY_STRING;
0749: String[] conversions = StringUtils.split(
0750: fieldConversions,
0751: RiceConstants.FIELD_CONVERSIONS_SEPERATOR);
0752:
0753: for (int l = 0; l < conversions.length; l++) {
0754: String conversion = conversions[l];
0755: String[] conversionPair = StringUtils
0756: .split(
0757: conversion,
0758: RiceConstants.FIELD_CONVERSION_PAIR_SEPERATOR);
0759: String conversionFrom = conversionPair[0];
0760: String conversionTo = conversionPair[1];
0761: conversionTo = RiceConstants.MAINTENANCE_NEW_MAINTAINABLE
0762: + conversionTo;
0763: newFieldConversions += (conversionFrom
0764: + RiceConstants.FIELD_CONVERSION_PAIR_SEPERATOR + conversionTo);
0765:
0766: if (l < conversions.length) {
0767: newFieldConversions += RiceConstants.FIELD_CONVERSIONS_SEPERATOR;
0768: }
0769: }
0770:
0771: field.setFieldConversions(newFieldConversions);
0772: }
0773:
0774: if (Field.KUALIUSER.equals(field.getFieldType())) {
0775: // prefix the personNameAttributeName
0776: int suffixIndex = field.getPropertyName().indexOf(
0777: field.getUserIdAttributeName());
0778: if (suffixIndex != -1) {
0779: field.setPersonNameAttributeName(field
0780: .getPropertyName()
0781: .substring(0, suffixIndex)
0782: + field.getPersonNameAttributeName());
0783: } else {
0784: field.setPersonNameAttributeName(namePrefix
0785: + field.getPersonNameAttributeName());
0786: }
0787:
0788: // TODO: do we need to prefix the universalIdAttributeName in Field as well?
0789: }
0790:
0791: // if lookupParameters specified, prefix with new constant
0792: if (StringUtils.isNotBlank(field.getLookupParameters())) {
0793: String lookupParameters = field.getLookupParameters();
0794: String newLookupParameters = RiceConstants.EMPTY_STRING;
0795: String[] conversions = StringUtils.split(
0796: lookupParameters,
0797: RiceConstants.FIELD_CONVERSIONS_SEPERATOR);
0798:
0799: for (int m = 0; m < conversions.length; m++) {
0800: String conversion = conversions[m];
0801: String[] conversionPair = StringUtils
0802: .split(
0803: conversion,
0804: RiceConstants.FIELD_CONVERSION_PAIR_SEPERATOR);
0805: String conversionFrom = conversionPair[0];
0806: String conversionTo = conversionPair[1];
0807: conversionFrom = RiceConstants.MAINTENANCE_NEW_MAINTAINABLE
0808: + conversionFrom;
0809: newLookupParameters += (conversionFrom
0810: + RiceConstants.FIELD_CONVERSION_PAIR_SEPERATOR + conversionTo);
0811:
0812: if (m < conversions.length) {
0813: newLookupParameters += RiceConstants.FIELD_CONVERSIONS_SEPERATOR;
0814: }
0815: }
0816:
0817: field.setLookupParameters(newLookupParameters);
0818: }
0819:
0820: // CONTAINER field types have nested rows and fields that need setup for the form
0821: if (Field.CONTAINER.equals(field.getFieldType())) {
0822: List containerRows = field.getContainerRows();
0823: List fixedRows = new ArrayList();
0824:
0825: for (Iterator iter = containerRows.iterator(); iter
0826: .hasNext();) {
0827: Row containerRow = (Row) iter.next();
0828: List containerFields = containerRow.getFields();
0829: List fixedFields = new ArrayList();
0830:
0831: for (Iterator iterator = containerFields.iterator(); iterator
0832: .hasNext();) {
0833: Field containerField = (Field) iterator.next();
0834: containerField = fixFieldForForm(
0835: containerField, keyFieldNames,
0836: namePrefix, maintenanceAction,
0837: readOnly, auths);
0838: fixedFields.add(containerField);
0839: }
0840:
0841: fixedRows.add(new Row(fixedFields));
0842: }
0843:
0844: field.setContainerRows(fixedRows);
0845: }
0846: }
0847: return field;
0848: }
0849:
0850: public static void applyAuthorization(Field field,
0851: MaintenanceDocumentAuthorizations auths) {
0852:
0853: // only apply this on the newMaintainable
0854: if (field.getPropertyName().startsWith(
0855: RiceConstants.MAINTENANCE_NEW_MAINTAINABLE)) {
0856:
0857: // get just the actual fieldName, with the document.newMaintainableObject, etc etc removed
0858: String fieldName = field.getPropertyName()
0859: .substring(
0860: RiceConstants.MAINTENANCE_NEW_MAINTAINABLE
0861: .length());
0862:
0863: // if the field is restricted somehow
0864: if (auths.hasAuthFieldRestricted(fieldName)) {
0865: FieldAuthorization fieldAuth = auths
0866: .getAuthFieldAuthorization(fieldName);
0867:
0868: // if its an editable field, allow decreasing availability to readonly or hidden
0869: if (Field.isInputField(field.getFieldType())
0870: || field.getFieldType().equalsIgnoreCase(
0871: Field.CHECKBOX)) {
0872:
0873: // only touch the field if the restricted type is hidden or readonly
0874: if (fieldAuth.isReadOnly()) {
0875: if (!field.isReadOnly()) {
0876: field.setReadOnly(true);
0877: }
0878: }
0879:
0880: else if (fieldAuth.isHidden()) {
0881: if (field.getFieldType() != Field.HIDDEN) {
0882: field.setFieldType(Field.HIDDEN);
0883: }
0884: }
0885: }
0886:
0887: // if the field is readOnly, and the authorization says it should be hidden,
0888: // then restrict it
0889: if (field.isReadOnly() && fieldAuth.isHidden()) {
0890: field.setFieldType(Field.HIDDEN);
0891: }
0892: }
0893: // special check for old maintainable - need to ensure that fields hidden on the
0894: // "new" side are also hidden on the old side
0895: } else if (field.getPropertyName().startsWith(
0896: RiceConstants.MAINTENANCE_OLD_MAINTAINABLE)) {
0897: // get just the actual fieldName, with the document.oldMaintainableObject, etc etc removed
0898: String fieldName = field.getPropertyName()
0899: .substring(
0900: RiceConstants.MAINTENANCE_OLD_MAINTAINABLE
0901: .length());
0902: // if the field is restricted somehow
0903: if (auths.hasAuthFieldRestricted(fieldName)) {
0904: FieldAuthorization fieldAuth = auths
0905: .getAuthFieldAuthorization(fieldName);
0906:
0907: if (fieldAuth.isHidden()) {
0908: field.setFieldType(Field.HIDDEN);
0909: }
0910: }
0911: }
0912: }
0913:
0914: /**
0915: * Merges together sections of the old maintainable and new maintainable.
0916: *
0917: * @param oldSections
0918: * @param newSections
0919: * @param keyFieldNames
0920: * @param maintenanceAction
0921: * @param readOnly
0922: * @return List of Section objects
0923: */
0924: public static List meshSections(List oldSections, List newSections,
0925: List keyFieldNames, String maintenanceAction,
0926: boolean readOnly, MaintenanceDocumentAuthorizations auths) {
0927: List meshedSections = new ArrayList();
0928:
0929: for (int i = 0; i < newSections.size(); i++) {
0930: Section maintSection = (Section) newSections.get(i);
0931: List sectionRows = maintSection.getRows();
0932: Section oldMaintSection = (Section) oldSections.get(i);
0933: List oldSectionRows = oldMaintSection.getRows();
0934: List<Row> meshedRows = new ArrayList();
0935: meshedRows = meshRows(oldSectionRows, sectionRows,
0936: keyFieldNames, maintenanceAction, readOnly, auths);
0937: maintSection.setRows(meshedRows);
0938: if (StringUtils.isBlank(maintSection.getErrorKey())) {
0939: maintSection.setErrorKey(MaintenanceUtils
0940: .generateErrorKeyForSection(maintSection));
0941: }
0942: meshedSections.add(maintSection);
0943: }
0944:
0945: return meshedSections;
0946: }
0947:
0948: /**
0949: * This method is a helper method for createRowsForNewFields. It puts together all the fields that should exist in a row after
0950: * calling the fixFieldForForm for the other necessary prefixing and setting up of the fields.
0951: *
0952: * @param newFields
0953: * @param keyFieldNames
0954: * @param maintenanceAction
0955: * @param readOnly
0956: * @param auths
0957: * @return a List of Field
0958: */
0959: private static List<Field> arrangeNewFields(List newFields,
0960: List keyFieldNames, String maintenanceAction,
0961: boolean readOnly, MaintenanceDocumentAuthorizations auths) {
0962: List<Field> results = new ArrayList();
0963: for (int k = 0; k < newFields.size(); k++) {
0964: Field newMaintField = (Field) newFields.get(k);
0965: String propertyName = newMaintField.getPropertyName();
0966: newMaintField = FieldUtils.fixFieldForForm(newMaintField,
0967: keyFieldNames,
0968: RiceConstants.MAINTENANCE_NEW_MAINTAINABLE,
0969: maintenanceAction, readOnly, auths);
0970:
0971: results.add(newMaintField);
0972: }
0973: return results;
0974: }
0975:
0976: /**
0977: * Merges together rows of an old maintainable section and new maintainable section.
0978: *
0979: * @param oldRows
0980: * @param newRows
0981: * @param keyFieldNames
0982: * @param maintenanceAction
0983: * @param readOnly
0984: * @return List of Row objects
0985: */
0986: public static List meshRows(List oldRows, List newRows,
0987: List keyFieldNames, String maintenanceAction,
0988: boolean readOnly, MaintenanceDocumentAuthorizations auths) {
0989: List<Row> meshedRows = new ArrayList<Row>();
0990:
0991: for (int j = 0; j < newRows.size(); j++) {
0992: Row sectionRow = (Row) newRows.get(j);
0993: List rowFields = sectionRow.getFields();
0994: Row oldSectionRow = null;
0995: List oldRowFields = new ArrayList();
0996:
0997: if (null != oldRows && oldRows.size() > j) {
0998: oldSectionRow = (Row) oldRows.get(j);
0999: oldRowFields = oldSectionRow.getFields();
1000: }
1001:
1002: List meshedFields = meshFields(oldRowFields, rowFields,
1003: keyFieldNames, maintenanceAction, readOnly, auths);
1004: if (meshedFields.size() > 0) {
1005: Row meshedRow = new Row(meshedFields);
1006: if (sectionRow.isHidden()) {
1007: meshedRow.setHidden(true);
1008: }
1009:
1010: meshedRows.add(meshedRow);
1011: }
1012: }
1013:
1014: return meshedRows;
1015: }
1016:
1017: /**
1018: * Merges together fields and an old maintainble row and new maintainable row, for each field call fixFieldForForm.
1019: *
1020: * @param oldFields
1021: * @param newFields
1022: * @param keyFieldNames
1023: * @param maintenanceAction
1024: * @param readOnly
1025: * @return List of Field objects
1026: */
1027: public static List meshFields(List oldFields, List newFields,
1028: List keyFieldNames, String maintenanceAction,
1029: boolean readOnly, MaintenanceDocumentAuthorizations auths) {
1030: List meshedFields = new ArrayList();
1031:
1032: List newFieldsToMerge = new ArrayList();
1033: List oldFieldsToMerge = new ArrayList();
1034:
1035: for (int k = 0; k < newFields.size(); k++) {
1036: Field newMaintField = (Field) newFields.get(k);
1037: String propertyName = newMaintField.getPropertyName();
1038: // If this is an add button, then we have to have only this field for the entire row.
1039: if (Field.IMAGE_SUBMIT.equals(newMaintField.getFieldType())) {
1040: meshedFields.add(newMaintField);
1041: } else if (Field.CONTAINER.equals(newMaintField
1042: .getFieldType())) {
1043: if (oldFields.size() > k) {
1044: Field oldMaintField = (Field) oldFields.get(k);
1045: newMaintField = meshContainerFields(oldMaintField,
1046: newMaintField, keyFieldNames,
1047: maintenanceAction, readOnly, auths);
1048: } else {
1049: newMaintField = meshContainerFields(newMaintField,
1050: newMaintField, keyFieldNames,
1051: maintenanceAction, readOnly, auths);
1052: }
1053: meshedFields.add(newMaintField);
1054: } else {
1055: newMaintField = FieldUtils.fixFieldForForm(
1056: newMaintField, keyFieldNames,
1057: RiceConstants.MAINTENANCE_NEW_MAINTAINABLE,
1058: maintenanceAction, readOnly, auths);
1059: // add old fields for edit
1060: if (RiceConstants.MAINTENANCE_EDIT_ACTION
1061: .equals(maintenanceAction)
1062: || RiceConstants.MAINTENANCE_COPY_ACTION
1063: .equals(maintenanceAction)) {
1064: Field oldMaintField = (Field) oldFields.get(k);
1065: oldMaintField = FieldUtils.fixFieldForForm(
1066: oldMaintField, keyFieldNames,
1067: RiceConstants.MAINTENANCE_OLD_MAINTAINABLE,
1068: maintenanceAction, true, auths);
1069: oldFieldsToMerge.add(oldMaintField);
1070:
1071: // compare values for change, and set new maintainable fields for highlighting
1072: // no point in highlighting the hidden fields, since they won't be rendered anyways
1073: if (!StringUtils.equalsIgnoreCase(newMaintField
1074: .getPropertyValue(), oldMaintField
1075: .getPropertyValue())
1076: && !Field.HIDDEN.equals(newMaintField
1077: .getFieldType())) {
1078: newMaintField.setHighlightField(true);
1079: }
1080: }
1081:
1082: newFieldsToMerge.add(newMaintField);
1083:
1084: for (Iterator iter = oldFieldsToMerge.iterator(); iter
1085: .hasNext();) {
1086: Field element = (Field) iter.next();
1087: meshedFields.add(element);
1088: }
1089:
1090: for (Iterator iter = newFieldsToMerge.iterator(); iter
1091: .hasNext();) {
1092: Field element = (Field) iter.next();
1093: meshedFields.add(element);
1094: }
1095: }
1096: }
1097: return meshedFields;
1098: }
1099:
1100: /**
1101: * Determines whether field level help is enabled for the field corresponding to the businessObjectClass and attribute name
1102: *
1103: * If this value is true, then the field level help will be enabled.
1104: * If false, then whether a field is enabled is determined by the value returned by {@link #isLookupFieldLevelHelpDisabled(Class, String)} and the system-wide
1105: * parameter setting. Note that if a field is read-only, that may cause field-level help to not be rendered.
1106: *
1107: * @param businessObjectClass the looked up class
1108: * @param attributeName the attribute for the field
1109: * @return true if field level help is enabled, false if the value of this method should NOT be used to determine whether this method's return value
1110: * affects the enablement of field level help
1111: */
1112: protected static boolean isLookupFieldLevelHelpEnabled(
1113: Class businessObjectClass, String attributeName) {
1114: return false;
1115: }
1116:
1117: /**
1118: * Determines whether field level help is disabled for the field corresponding to the businessObjectClass and attribute name
1119: *
1120: * If this value is true and {@link #isLookupFieldLevelHelpEnabled(Class, String)} returns false,
1121: * then the field level help will not be rendered. If both this and {@link #isLookupFieldLevelHelpEnabled(Class, String)} return false, then the system-wide
1122: * setting will determine whether field level help is enabled. Note that if a field is read-only, that may cause field-level help to not be rendered.
1123: *
1124: * @param businessObjectClass the looked up class
1125: * @param attributeName the attribute for the field
1126: * @return true if field level help is disabled, false if the value of this method should NOT be used to determine whether this method's return value
1127: * affects the enablement of field level help
1128: */
1129: protected static boolean isLookupFieldLevelHelpDisabled(
1130: Class businessObjectClass, String attributeName) {
1131: return false;
1132: }
1133:
1134: public static List createAndPopulateFieldsForLookup(
1135: List<String> lookupFieldAttributeList,
1136: List<String> readOnlyFieldsList, Class businessObjectClass)
1137: throws InstantiationException, IllegalAccessException {
1138: List<Field> fields = new ArrayList<Field>();
1139: for (Iterator iter = lookupFieldAttributeList.iterator(); iter
1140: .hasNext();) {
1141: String attributeName = (String) iter.next();
1142: Field field = FieldUtils.getPropertyField(
1143: businessObjectClass, attributeName, true);
1144:
1145: // TODO: This makes no sense, why do we pass it in and then return the same thing
1146: // back to us?
1147: field = LookupUtils.setFieldQuickfinder(
1148: (BusinessObject) businessObjectClass.newInstance(),
1149: attributeName, field, lookupFieldAttributeList);
1150:
1151: // overwrite maxLength to allow for wildcards and ranges in the select
1152: field.setMaxLength(100);
1153: fields.add(field);
1154:
1155: // set default value
1156: String defaultValue = businessObjectMetaDataService
1157: .getLookupFieldDefaultValue(businessObjectClass,
1158: attributeName);
1159: if (defaultValue != null) {
1160: field.setPropertyValue(defaultValue);
1161: field.setDefaultValue(defaultValue);
1162: }
1163:
1164: Class defaultValueFinderClass = businessObjectMetaDataService
1165: .getLookupFieldDefaultValueFinderClass(
1166: businessObjectClass, attributeName);
1167: if (defaultValueFinderClass != null) {
1168: field
1169: .setPropertyValue(((ValueFinder) defaultValueFinderClass
1170: .newInstance()).getValue());
1171: field
1172: .setDefaultValue(((ValueFinder) defaultValueFinderClass
1173: .newInstance()).getValue());
1174: }
1175: if (readOnlyFieldsList != null
1176: && readOnlyFieldsList.contains(field
1177: .getPropertyName())) {
1178: field.setReadOnly(true);
1179: }
1180:
1181: field
1182: .setFieldLevelHelpEnabled(isLookupFieldLevelHelpEnabled(
1183: businessObjectClass, attributeName));
1184: field
1185: .setFieldLevelHelpDisabled(isLookupFieldLevelHelpDisabled(
1186: businessObjectClass, attributeName));
1187: }
1188: return fields;
1189: }
1190:
1191: private static Field meshContainerFields(Field oldMaintField,
1192: Field newMaintField, List keyFieldNames,
1193: String maintenanceAction, boolean readOnly,
1194: MaintenanceDocumentAuthorizations auths) {
1195: List resultingRows = new ArrayList();
1196: resultingRows.addAll(meshRows(oldMaintField.getContainerRows(),
1197: newMaintField.getContainerRows(), keyFieldNames,
1198: maintenanceAction, readOnly, auths));
1199: Field resultingField = newMaintField;
1200: resultingField.setFieldType(Field.CONTAINER);
1201:
1202: // save the summary info
1203: resultingField.setContainerElementName(newMaintField
1204: .getContainerElementName());
1205: resultingField.setContainerDisplayFields(newMaintField
1206: .getContainerDisplayFields());
1207: resultingField.setNumberOfColumnsForCollection(newMaintField
1208: .getNumberOfColumnsForCollection());
1209:
1210: resultingField.setContainerRows(resultingRows);
1211: List resultingRowsList = newMaintField.getContainerRows();
1212: if (resultingRowsList.size() > 0) {
1213: List resultingFieldsList = ((Row) resultingRowsList.get(0))
1214: .getFields();
1215: if (resultingFieldsList.size() > 0) {
1216: // todo: assign the correct propertyName to the container in the first place. For now, I'm wary of the weird usages
1217: // of constructContainerField().
1218: String containedFieldName = ((Field) (resultingFieldsList
1219: .get(0))).getPropertyName();
1220: resultingField.setPropertyName(containedFieldName
1221: .substring(0, containedFieldName
1222: .lastIndexOf('.')));
1223: }
1224: } else {
1225: resultingField.setPropertyName(oldMaintField
1226: .getPropertyName());
1227: }
1228: return resultingField;
1229: }
1230:
1231: /**
1232: * This method modifies the passed in field so that it may be used to render a multiple values lookup button
1233: *
1234: * @param field this object will be modified by this method
1235: * @param parents
1236: * @param definition
1237: */
1238: static final public void modifyFieldToSupportMultipleValueLookups(
1239: Field field, String parents,
1240: MaintainableCollectionDefinition definition) {
1241: field.setMultipleValueLookedUpCollectionName(parents
1242: + definition.getName());
1243: field.setMultipleValueLookupClassName(definition
1244: .getSourceClassName());
1245: field.setMultipleValueLookupClassLabel(dictionaryService
1246: .getDataDictionary().getBusinessObjectEntry(
1247: definition.getSourceClassName())
1248: .getObjectLabel());
1249: }
1250:
1251: /**
1252: * Returns whether the passed in collection has been properly configured in the maint doc dictionary to support multiple value
1253: * lookups.
1254: *
1255: * @param definition
1256: * @return
1257: */
1258: static final public boolean isCollectionMultipleLookupEnabled(
1259: MaintainableCollectionDefinition definition) {
1260: return definition.getSourceClassName() != null
1261: && definition.getSourceClassName().length() > 0
1262: && definition.isIncludeMultipleLookupLine();
1263: }
1264:
1265: /**
1266: * This method removes any duplicating spacing (internal or on the ends) from a String, meant to be exposed as a tag library
1267: * function.
1268: *
1269: * @param s String to remove duplicate spacing from.
1270: * @return String without duplicate spacing.
1271: */
1272: public static String scrubWhitespace(String s) {
1273: return s.replaceAll("(\\s)(\\s+)", " ");
1274: }
1275: }
|