View Javadoc

1   /**
2    * Copyright 2005-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.util;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.encryption.EncryptionService;
22  import org.kuali.rice.core.api.mo.common.active.MutableInactivatable;
23  import org.kuali.rice.core.api.uif.AttributeLookupSettings;
24  import org.kuali.rice.core.api.uif.Control;
25  import org.kuali.rice.core.api.uif.DataType;
26  import org.kuali.rice.core.api.uif.RemotableAbstractControl;
27  import org.kuali.rice.core.api.uif.RemotableAbstractWidget;
28  import org.kuali.rice.core.api.uif.RemotableAttributeField;
29  import org.kuali.rice.core.api.uif.RemotableAttributeLookupSettings;
30  import org.kuali.rice.core.api.uif.RemotableCheckboxGroup;
31  import org.kuali.rice.core.api.uif.RemotableDatepicker;
32  import org.kuali.rice.core.api.uif.RemotableHiddenInput;
33  import org.kuali.rice.core.api.uif.RemotablePasswordInput;
34  import org.kuali.rice.core.api.uif.RemotableQuickFinder;
35  import org.kuali.rice.core.api.uif.RemotableRadioButtonGroup;
36  import org.kuali.rice.core.api.uif.RemotableSelect;
37  import org.kuali.rice.core.api.uif.RemotableTextExpand;
38  import org.kuali.rice.core.api.uif.RemotableTextInput;
39  import org.kuali.rice.core.api.uif.RemotableTextarea;
40  import org.kuali.rice.core.api.util.ClassLoaderUtils;
41  import org.kuali.rice.core.api.util.ConcreteKeyValue;
42  import org.kuali.rice.core.api.util.KeyValue;
43  import org.kuali.rice.core.web.format.FormatException;
44  import org.kuali.rice.core.web.format.Formatter;
45  import org.kuali.rice.kew.api.KewApiConstants;
46  import org.kuali.rice.kim.api.identity.Person;
47  import org.kuali.rice.kns.datadictionary.BusinessObjectEntry;
48  import org.kuali.rice.kns.datadictionary.FieldDefinition;
49  import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
50  import org.kuali.rice.kns.datadictionary.control.ButtonControlDefinition;
51  import org.kuali.rice.kns.datadictionary.control.CurrencyControlDefinition;
52  import org.kuali.rice.kns.datadictionary.control.KualiUserControlDefinition;
53  import org.kuali.rice.kns.datadictionary.control.LinkControlDefinition;
54  import org.kuali.rice.kns.document.authorization.FieldRestriction;
55  import org.kuali.rice.kns.document.authorization.MaintenanceDocumentRestrictions;
56  import org.kuali.rice.kns.inquiry.Inquirable;
57  import org.kuali.rice.kns.lookup.HtmlData;
58  import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData;
59  import org.kuali.rice.kns.lookup.LookupUtils;
60  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
61  import org.kuali.rice.kns.service.KNSServiceLocator;
62  import org.kuali.rice.kns.web.comparator.CellComparatorHelper;
63  import org.kuali.rice.kns.web.ui.Column;
64  import org.kuali.rice.kns.web.ui.Field;
65  import org.kuali.rice.kns.web.ui.PropertyRenderingConfigElement;
66  import org.kuali.rice.kns.web.ui.Row;
67  import org.kuali.rice.kns.web.ui.Section;
68  import org.kuali.rice.krad.bo.BusinessObject;
69  import org.kuali.rice.krad.bo.DataObjectRelationship;
70  import org.kuali.rice.krad.bo.KualiCode;
71  import org.kuali.rice.krad.bo.PersistableBusinessObject;
72  import org.kuali.rice.krad.datadictionary.control.ControlDefinition;
73  import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException;
74  import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
75  import org.kuali.rice.krad.keyvalues.IndicatorValuesFinder;
76  import org.kuali.rice.krad.keyvalues.KeyValuesFinder;
77  import org.kuali.rice.krad.keyvalues.PersistableBusinessObjectValuesFinder;
78  import org.kuali.rice.krad.service.BusinessObjectDictionaryService;
79  import org.kuali.rice.krad.service.DataDictionaryService;
80  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
81  import org.kuali.rice.krad.service.KualiModuleService;
82  import org.kuali.rice.krad.service.ModuleService;
83  import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils;
84  import org.kuali.rice.krad.util.GlobalVariables;
85  import org.kuali.rice.krad.util.KRADConstants;
86  import org.kuali.rice.krad.util.KRADPropertyConstants;
87  import org.kuali.rice.krad.util.MessageMap;
88  import org.kuali.rice.krad.util.ObjectUtils;
89  import org.kuali.rice.krad.valuefinder.ValueFinder;
90  
91  import java.lang.reflect.InvocationTargetException;
92  import java.security.GeneralSecurityException;
93  import java.util.ArrayList;
94  import java.util.Collection;
95  import java.util.Collections;
96  import java.util.HashMap;
97  import java.util.Iterator;
98  import java.util.List;
99  import java.util.Map;
100 
101 
102 /**
103  * This class is used to build Field objects from underlying data dictionary and general utility methods for handling fields.
104  */
105 public final class FieldUtils {
106     private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FieldUtils.class);
107     private static DataDictionaryService dataDictionaryService = null;
108     private static BusinessObjectMetaDataService businessObjectMetaDataService = null;
109     private static BusinessObjectDictionaryService businessObjectDictionaryService = null;
110     private static KualiModuleService kualiModuleService = null;
111     
112 	private FieldUtils() {
113 		throw new UnsupportedOperationException("do not call");
114 	}
115 
116     public static void setInquiryURL(Field field, BusinessObject bo, String propertyName) {
117         HtmlData inquiryHref = new AnchorHtmlData(KRADConstants.EMPTY_STRING, KRADConstants.EMPTY_STRING);
118 
119         Boolean b = getBusinessObjectDictionaryService().noInquiryFieldInquiry(bo.getClass(), propertyName);
120         if (b == null || !b.booleanValue()) {
121             Class<Inquirable> inquirableClass = getBusinessObjectDictionaryService().getInquirableClass(bo.getClass());
122             Boolean b2 = getBusinessObjectDictionaryService().forceLookupResultFieldInquiry(bo.getClass(), propertyName);
123             Inquirable inq = null;
124             try {
125                 if ( inquirableClass != null ) {
126                     inq = inquirableClass.newInstance();
127                 } else {
128                     inq = KNSServiceLocator.getKualiInquirable();
129                     if ( LOG.isDebugEnabled() ) {
130                         LOG.debug( "Default Inquirable Class: " + inq.getClass() );
131         }
132                 }
133 
134                 inquiryHref = inq.getInquiryUrl(bo, propertyName, null == b2 ? false : b2.booleanValue() );
135 
136             } catch ( Exception ex ) {
137                 LOG.error("unable to create inquirable to get inquiry URL", ex );
138             }
139         }
140 
141         field.setInquiryURL(inquiryHref);
142     }
143 
144 	/**
145 	 * Sets the control on the field based on the data dictionary definition
146 	 *
147 	 * @param businessObjectClass
148 	 *            - business object class for the field attribute
149 	 * @param attributeName
150 	 *            - name of the attribute whose {@link Field} is being set
151 	 * @param convertForLookup
152 	 *            - whether the field is being build for lookup search which impacts the control chosen
153 	 * @param field
154 	 *            - {@link Field} to set control on
155 	 */
156 	public static void setFieldControl(Class businessObjectClass, String attributeName, boolean convertForLookup,
157 			Field field) {
158 		ControlDefinition control = getDataDictionaryService().getAttributeControlDefinition(businessObjectClass,
159 				attributeName);
160 		String fieldType = Field.TEXT;
161 
162 		if (control != null) {
163 			if (control.isSelect()) {
164 				if (control.getScript() != null && control.getScript().length() > 0) {
165 					fieldType = Field.DROPDOWN_SCRIPT;
166 					field.setScript(control.getScript());
167 				} else {
168 					fieldType = Field.DROPDOWN;
169 				}
170 			}
171 
172 			if (control.isMultiselect()) {
173 				fieldType = Field.MULTISELECT;
174 			}
175 
176 			if (control.isCheckbox()) {
177 				fieldType = Field.CHECKBOX;
178 			}
179 
180 			if (control.isRadio()) {
181 				fieldType = Field.RADIO;
182 			}
183 
184 			if (control.isHidden()) {
185 				fieldType = Field.HIDDEN;
186 			}
187 
188 			if (control.isKualiUser()) {
189 				fieldType = Field.KUALIUSER;
190 				KualiUserControlDefinition kualiUserControl = (KualiUserControlDefinition) control;
191 				field.setUniversalIdAttributeName(kualiUserControl.getUniversalIdAttributeName());
192 				field.setUserIdAttributeName(kualiUserControl.getUserIdAttributeName());
193 				field.setPersonNameAttributeName(kualiUserControl.getPersonNameAttributeName());
194 			}
195 
196 			if (control.isWorkflowWorkgroup()) {
197 				fieldType = Field.WORKFLOW_WORKGROUP;
198 			}
199 
200 			if (control.isFile()) {
201 				fieldType = Field.FILE;
202 			}
203 
204 			if (control.isTextarea() && !convertForLookup) {
205 				fieldType = Field.TEXT_AREA;
206 			}
207 
208 			if (control.isLookupHidden()) {
209 				fieldType = Field.LOOKUP_HIDDEN;
210 			}
211 
212 			if (control.isLookupReadonly()) {
213 				fieldType = Field.LOOKUP_READONLY;
214 			}
215 
216 			if (control.isCurrency()) {
217 				fieldType = Field.CURRENCY;
218 			}
219 
220 			if (control.isButton()) {
221 				fieldType = Field.BUTTON;
222 			}
223 
224 			if (control.isLink()) {
225 				fieldType = Field.LINK;
226 			}
227 
228 			if (Field.CURRENCY.equals(fieldType) && control instanceof CurrencyControlDefinition) {
229 				CurrencyControlDefinition currencyControl = (CurrencyControlDefinition) control;
230 				field.setStyleClass("amount");
231 				field.setSize(currencyControl.getSize());
232 				field.setFormattedMaxLength(currencyControl.getFormattedMaxLength());
233 			}
234 
235 			// for text controls, set size attribute
236 			if (Field.TEXT.equals(fieldType)) {
237 				Integer size = control.getSize();
238 				if (size != null) {
239 					field.setSize(size.intValue());
240 				} else {
241 					field.setSize(30);
242 				}
243 				field.setDatePicker(control.isDatePicker());
244 				field.setRanged(control.isRanged());
245 			}
246 
247 			if (Field.WORKFLOW_WORKGROUP.equals(fieldType)) {
248 				Integer size = control.getSize();
249 				if (size != null) {
250 					field.setSize(size.intValue());
251 				} else {
252 					field.setSize(30);
253 				}
254 			}
255 
256 			// for text area controls, set rows and cols attributes
257 			if (Field.TEXT_AREA.equals(fieldType)) {
258 				Integer rows = control.getRows();
259 				if (rows != null) {
260 					field.setRows(rows.intValue());
261 				} else {
262 					field.setRows(3);
263 				}
264 
265 				Integer cols = control.getCols();
266 				if (cols != null) {
267 					field.setCols(cols.intValue());
268 				} else {
269 					field.setCols(40);
270 				}
271 				field.setExpandedTextArea(control.isExpandedTextArea());
272 			}
273 
274             if (Field.MULTISELECT.equals(fieldType)) {
275                 Integer size = control.getSize();
276 				if (size != null) {
277 					field.setSize(size.intValue());
278 				}
279             }
280 
281 			// for dropdown and radio, get instance of specified KeyValuesFinder and set field values
282 			if (Field.DROPDOWN.equals(fieldType) || Field.RADIO.equals(fieldType)
283 					|| Field.DROPDOWN_SCRIPT.equals(fieldType)
284 					|| Field.MULTISELECT.equals(fieldType)) {
285 				String keyFinderClassName = control.getValuesFinderClass();
286 
287 				if (StringUtils.isNotBlank(keyFinderClassName)) {
288 					try {
289 						Class keyFinderClass = ClassLoaderUtils.getClass(keyFinderClassName);
290 						KeyValuesFinder finder = (KeyValuesFinder) keyFinderClass.newInstance();
291 
292 						if (finder != null) {
293 							if (finder instanceof PersistableBusinessObjectValuesFinder) {
294 								((PersistableBusinessObjectValuesFinder) finder)
295 										.setBusinessObjectClass(ClassLoaderUtils.getClass(control
296                                                 .getBusinessObjectClass()));
297 								((PersistableBusinessObjectValuesFinder) finder).setKeyAttributeName(control
298                                         .getKeyAttribute());
299 								((PersistableBusinessObjectValuesFinder) finder).setLabelAttributeName(control
300 										.getLabelAttribute());
301 								if (control.getIncludeBlankRow() != null) {
302 									((PersistableBusinessObjectValuesFinder) finder).setIncludeBlankRow(control
303 											.getIncludeBlankRow());
304 								}
305 								((PersistableBusinessObjectValuesFinder) finder).setIncludeKeyInDescription(control
306                                         .getIncludeKeyInLabel());
307 							}
308 							field.setFieldValidValues(finder.getKeyValues());
309 							field.setFieldInactiveValidValues(finder.getKeyValues(false));
310 						}
311 					} catch (InstantiationException e) {
312 						LOG.error("Unable to get new instance of finder class: " + keyFinderClassName);
313 						throw new RuntimeException("Unable to get new instance of finder class: " + keyFinderClassName);
314 					} catch (IllegalAccessException e) {
315 						LOG.error("Unable to get new instance of finder class: " + keyFinderClassName);
316 						throw new RuntimeException("Unable to get new instance of finder class: " + keyFinderClassName);
317 					}
318 				}
319 			}
320 
321 			if (Field.CHECKBOX.equals(fieldType) && convertForLookup) {
322 				fieldType = Field.RADIO;
323 				field.setFieldValidValues(IndicatorValuesFinder.INSTANCE.getKeyValues());
324 			}
325 
326 			// for button control
327 			if (Field.BUTTON.equals(fieldType)) {
328 				ButtonControlDefinition buttonControl = (ButtonControlDefinition) control;
329 				field.setImageSrc(buttonControl.getImageSrc());
330 				field.setStyleClass(buttonControl.getStyleClass());
331 			}
332 
333 			// for link control
334 			if (Field.LINK.equals(fieldType)) {
335 				LinkControlDefinition linkControl = (LinkControlDefinition) control;
336 				field.setStyleClass(linkControl.getStyleClass());
337 				field.setTarget(linkControl.getTarget());
338 				field.setHrefText(linkControl.getHrefText());
339 			}
340 
341 		}
342 
343 		field.setFieldType(fieldType);
344 	}
345 
346 
347     /**
348      * Builds up a Field object based on the propertyName and business object class.
349      *
350      * See KULRICE-2480 for info on convertForLookup flag
351      *
352      */
353     public static Field getPropertyField(Class businessObjectClass, String attributeName, boolean convertForLookup) {
354         Field field = new Field();
355         field.setPropertyName(attributeName);
356         field.setFieldLabel(getDataDictionaryService().getAttributeLabel(businessObjectClass, attributeName));
357 
358         setFieldControl(businessObjectClass, attributeName, convertForLookup, field);
359 
360         Boolean fieldRequired = getBusinessObjectDictionaryService().getLookupAttributeRequired(businessObjectClass, attributeName);
361         if (fieldRequired != null) {
362             field.setFieldRequired(fieldRequired.booleanValue());
363         }
364 
365         Integer maxLength = getDataDictionaryService().getAttributeMaxLength(businessObjectClass, attributeName);
366         if (maxLength != null) {
367             field.setMaxLength(maxLength.intValue());
368         }
369 
370         Boolean upperCase = null;
371         try {
372             upperCase = getDataDictionaryService().getAttributeForceUppercase(businessObjectClass, attributeName);
373         }
374         catch (UnknownBusinessClassAttributeException t) {
375         	// do nothing
376         	LOG.warn( "UnknownBusinessClassAttributeException in fieldUtils.getPropertyField() : " + t.getMessage() );
377         }
378         if (upperCase != null) {
379             field.setUpperCase(upperCase.booleanValue());
380         }
381         
382 		if (!businessObjectClass.isInterface()) {
383 			try {
384 				field.setFormatter(
385                         ObjectUtils.getFormatterWithDataDictionary(businessObjectClass.newInstance(), attributeName));
386 			} catch (InstantiationException e) {
387 				LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e);
388 				// just swallow exception and leave formatter blank
389 			} catch (IllegalAccessException e) {
390 				LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e);
391 				// just swallow exception and leave formatter blank
392 			}
393 		}
394 
395         // set Field help properties
396         field.setBusinessObjectClassName(businessObjectClass.getName());
397         field.setFieldHelpName(attributeName);
398         field.setFieldHelpSummary(getDataDictionaryService().getAttributeSummary(businessObjectClass, attributeName));
399 
400         return field;
401     }
402 
403 	/**
404 	 * For attributes that are codes (determined by whether they have a
405 	 * reference to a KualiCode bo and similar naming) sets the name as an
406 	 * additional display property
407 	 * 
408 	 * @param businessObjectClass -
409 	 *            class containing attribute
410 	 * @param attributeName - 
411 	 *            name of attribute in the business object
412 	 * @param field - 
413 	 *            property display element
414 	 */
415 	public static void setAdditionalDisplayPropertyForCodes(Class businessObjectClass, String attributeName, PropertyRenderingConfigElement field) {
416 		try {
417 			DataObjectRelationship relationship = getBusinessObjectMetaDataService().getBusinessObjectRelationship(
418 					(BusinessObject) businessObjectClass.newInstance(), attributeName);
419 
420 			if (relationship != null && attributeName.startsWith(relationship.getParentAttributeName())
421 					&& KualiCode.class.isAssignableFrom(relationship.getRelatedClass())) {
422 				field.setAdditionalDisplayPropertyName(relationship.getParentAttributeName() + "."
423 						+ KRADPropertyConstants.NAME);
424 			}
425 		} catch (Exception e) {
426 			throw new RuntimeException("Cannot get new instance of class to check for KualiCode references: "
427 					+ e.getMessage());
428 		}
429 	}
430 
431 
432     /**
433      * Wraps each Field in the list into a Row.
434      *
435      * @param fields
436      * @return List of Row objects
437      */
438     public static List wrapFields(List fields) {
439         return wrapFields(fields, KRADConstants.DEFAULT_NUM_OF_COLUMNS);
440     }
441 
442     /**
443      * This method is to implement multiple columns where the numberOfColumns is obtained from data dictionary.
444      *
445      * @param fields
446      * @param numberOfColumns
447      * @return
448      */
449     public static List<Row> wrapFields(List<Field> fields, int numberOfColumns) {
450 
451         List<Row> rows = new ArrayList();
452         List<Field> fieldOnlyList = new ArrayList();
453 
454         List<Field> visableFields = getVisibleFields(fields);
455     	List<Field> nonVisableFields = getNonVisibleFields(fields);
456 
457         int fieldsPosition = 0;
458         for (Field element : visableFields) {
459             if (Field.SUB_SECTION_SEPARATOR.equals(element.getFieldType()) || Field.CONTAINER.equals(element.getFieldType())) {
460                 fieldsPosition = createBlankSpace(fieldOnlyList, rows, numberOfColumns, fieldsPosition);
461                 List fieldList = new ArrayList();
462                 fieldList.add(element);
463                 rows.add(new Row(fieldList));
464             }
465             else {
466                 if (fieldsPosition < numberOfColumns) {
467                     fieldOnlyList.add(element);
468                     fieldsPosition++;
469                 }
470                 else {
471                     rows.add(new Row(new ArrayList(fieldOnlyList)));
472                     fieldOnlyList.clear();
473                     fieldOnlyList.add(element);
474                     fieldsPosition = 1;
475                 }
476             }
477         }
478         createBlankSpace(fieldOnlyList, rows, numberOfColumns, fieldsPosition);
479 
480      // Add back the non Visible Rows
481     	if(nonVisableFields != null && !nonVisableFields.isEmpty()){
482     		Row nonVisRow = new Row();
483     		nonVisRow.setFields(nonVisableFields);
484     		rows.add(nonVisRow);
485     	}
486 
487 
488         return rows;
489     }
490 
491     private static List<Field> getVisibleFields(List<Field> fields){
492     	List<Field> rList = new ArrayList<Field>();
493 
494    		for(Field f: fields){
495    			if(!Field.HIDDEN.equals(f.getFieldType()) &&  !Field.BLANK_SPACE.equals(f.getFieldType())){
496    				rList.add(f);
497    			}
498    		}
499 
500     	return rList;
501     }
502 
503     private static List<Field> getNonVisibleFields(List<Field> fields){
504     	List<Field> rList = new ArrayList<Field>();
505 
506    		for(Field f: fields){
507    			if(Field.HIDDEN.equals(f.getFieldType()) || Field.BLANK_SPACE.equals(f.getFieldType())){
508    				rList.add(f);
509    			}
510    		}
511 
512     	return rList;
513     }
514 
515     /**
516      * This is a helper method to create and add a blank space to the fieldOnly List.
517      *
518      * @param fieldOnlyList
519      * @param rows
520      * @param numberOfColumns
521      * @return fieldsPosition
522      */
523     private static int createBlankSpace(List<Field> fieldOnlyList, List<Row> rows, int numberOfColumns, int fieldsPosition) {
524         int fieldOnlySize = fieldOnlyList.size();
525         if (fieldOnlySize > 0) {
526             for (int i = 0; i < (numberOfColumns - fieldOnlySize); i++) {
527                 Field empty = new Field();
528                 empty.setFieldType(Field.BLANK_SPACE);
529                 // Must be set or AbstractLookupableHelperServiceImpl::preprocessDateFields dies
530                 empty.setPropertyName(Field.BLANK_SPACE);
531                 fieldOnlyList.add(empty);
532             }
533             rows.add(new Row(new ArrayList(fieldOnlyList)));
534             fieldOnlyList.clear();
535             fieldsPosition = 0;
536         }
537         return fieldsPosition;
538     }
539 
540     /**
541      * Wraps list of fields into a Field of type CONTAINER
542      *
543      * @param name name for the field
544      * @param label label for the field
545      * @param fields list of fields that should be contained in the container
546      * @return Field of type CONTAINER
547      */
548     public static Field constructContainerField(String name, String label, List fields) {
549         return constructContainerField(name, label, fields, KRADConstants.DEFAULT_NUM_OF_COLUMNS);
550     }
551 
552     /**
553      * Wraps list of fields into a Field of type CONTAINER and arrange them into multiple columns.
554      *
555      * @param name name for the field
556      * @param label label for the field
557      * @param fields list of fields that should be contained in the container
558      * @param numberOfColumns the number of columns for each row that the fields should be arranged into
559      * @return Field of type CONTAINER
560      */
561     public static Field constructContainerField(String name, String label, List fields, int numberOfColumns) {
562         Field containerField = new Field();
563         containerField.setPropertyName(name);
564         containerField.setFieldLabel(label);
565         containerField.setFieldType(Field.CONTAINER);
566         containerField.setNumberOfColumnsForCollection(numberOfColumns);
567 
568         List rows = wrapFields(fields, numberOfColumns);
569         containerField.setContainerRows(rows);
570 
571         return containerField;
572     }
573 
574     /**
575      * Uses reflection to get the property names of the business object, then checks for a matching field property name. If found,
576      * takes the value of the business object property and populates the field value. Iterates through for all fields in the list.
577      *
578      * @param fields list of Field object to populate
579      * @param bo business object to get field values from
580      * @return List of fields with values populated from business object.
581      */
582     public static List<Field> populateFieldsFromBusinessObject(List<Field> fields, BusinessObject bo) {
583         List<Field> populatedFields = new ArrayList<Field>();
584 
585         if (bo instanceof PersistableBusinessObject) {
586         	((PersistableBusinessObject) bo).refreshNonUpdateableReferences();
587         }
588         
589         for (Iterator<Field> iter = fields.iterator(); iter.hasNext();) {
590             Field element = iter.next();
591             if (element.containsBOData()) {
592                 String propertyName = element.getPropertyName();
593 
594                 // See: https://test.kuali.org/jira/browse/KULCOA-1185
595                 // Properties that could not possibly be set by the BusinessObject should be ignored.
596                 // (https://test.kuali.org/jira/browse/KULRNE-4354; this code was killing the src attribute of IMAGE_SUBMITs).
597                 if (isPropertyNested(propertyName) && !isObjectTreeNonNullAllTheWayDown(bo, propertyName) && ((!element.getFieldType().equals(Field.IMAGE_SUBMIT)) && !(element.getFieldType().equals(Field.CONTAINER)) && (!element.getFieldType().equals(Field.QUICKFINDER)))) {
598                     element.setPropertyValue(null);
599                 }
600                 else if (PropertyUtils.isReadable(bo, propertyName)) {
601                 	populateReadableField(element, bo);
602                 }
603                 
604     			if (StringUtils.isNotBlank(element.getAlternateDisplayPropertyName())) {
605     				String alternatePropertyValue = ObjectUtils.getFormattedPropertyValueUsingDataDictionary(bo, element
606     						.getAlternateDisplayPropertyName());
607     				element.setAlternateDisplayPropertyValue(alternatePropertyValue);
608     			}
609 
610     			if (StringUtils.isNotBlank(element.getAdditionalDisplayPropertyName())) {
611     				String additionalPropertyValue = ObjectUtils.getFormattedPropertyValueUsingDataDictionary(bo, element
612     						.getAdditionalDisplayPropertyName());
613     				element.setAdditionalDisplayPropertyValue(additionalPropertyValue);
614     			}
615             }
616             populatedFields.add(element);
617         }
618 
619         return populatedFields;
620     }
621 
622     public static void populateReadableField(Field field, BusinessObject businessObject){
623 		Object obj = ObjectUtils.getNestedValue(businessObject, field.getPropertyName());
624 		if (obj != null) {
625 			String formattedValue = ObjectUtils.getFormattedPropertyValueUsingDataDictionary(businessObject, field.getPropertyName());
626 			field.setPropertyValue(formattedValue);
627         	
628             // for user fields, attempt to pull the principal ID and person's name from the source object
629             if ( field.getFieldType().equals(Field.KUALIUSER) ) {
630             	// this is supplemental, so catch and log any errors
631             	try {
632             		if ( StringUtils.isNotBlank(field.getUniversalIdAttributeName()) ) {
633             			Object principalId = ObjectUtils.getNestedValue(businessObject, field.getUniversalIdAttributeName());
634             			if ( principalId != null ) {
635             				field.setUniversalIdValue(principalId.toString());
636             			}
637             		}
638             		if ( StringUtils.isNotBlank(field.getPersonNameAttributeName()) ) {
639             			Object personName = ObjectUtils.getNestedValue(businessObject, field.getPersonNameAttributeName());
640             			if ( personName != null ) {
641             				field.setPersonNameValue( personName.toString() );
642             			}
643             		}
644             	} catch ( Exception ex ) {
645             		LOG.warn( "Unable to get principal ID or person name property in FieldBridge.", ex );
646             	}
647             }
648         }
649         
650         populateSecureField(field, obj);
651     }
652 
653     public static void populateSecureField(Field field, Object fieldValue){
654         // set encrypted & masked value if user does not have permission to see real value in UI
655         // element.isSecure() => a non-null AttributeSecurity object is set in the field
656         if (field.isSecure()) {
657             try {
658                 if (fieldValue != null && fieldValue.toString().endsWith(EncryptionService.HASH_POST_PREFIX)) {
659                 	field.setEncryptedValue(fieldValue.toString());
660                 }
661                 else {
662                 	field.setEncryptedValue(CoreApiServiceLocator.getEncryptionService().encrypt(fieldValue) + EncryptionService.ENCRYPTION_POST_PREFIX);
663                 }
664             }
665             catch (GeneralSecurityException e) {
666                 throw new RuntimeException("Unable to encrypt secure field " + e.getMessage());
667             }
668             //field.setDisplayMaskValue(field.getAttributeSecurity().getDisplayMaskValue(fieldValue));
669         }
670     }
671 
672     /**
673      * This method indicates whether or not propertyName refers to a nested attribute.
674      *
675      * @param propertyName
676      * @return true if propertyName refers to a nested property (e.g. "x.y")
677      */
678     static private boolean isPropertyNested(String propertyName) {
679         return -1 != propertyName.indexOf('.');
680     }
681 
682     /**
683      * This method verifies that all of the parent objects of propertyName are non-null.
684      *
685      * @param bo
686      * @param propertyName
687      * @return true if all parents are non-null, otherwise false
688      */
689 
690     static private boolean isObjectTreeNonNullAllTheWayDown(BusinessObject bo, String propertyName) {
691         String[] propertyParts = propertyName.split("\\.");
692 
693         StringBuffer property = new StringBuffer();
694         for (int i = 0; i < propertyParts.length - 1; i++) {
695 
696             property.append((0 == property.length()) ? "" : ".").append(propertyParts[i]);
697             try {
698                 if (null == PropertyUtils.getNestedProperty(bo, property.toString())) {
699                     return false;
700                 }
701             }
702             catch (Throwable t) {
703                 LOG.debug("Either getter or setter not specified for property \"" + property.toString() + "\"", t);
704                 return false;
705             }
706         }
707 
708         return true;
709 
710     }
711 
712     /**
713      * @param bo
714      * @param propertyName
715      * @return true if one (or more) of the intermediate objects in the given propertyName is null
716      */
717     private static boolean containsIntermediateNull(Object bo, String propertyName) {
718         boolean containsNull = false;
719 
720         if (StringUtils.contains(propertyName, ".")) {
721             String prefix = StringUtils.substringBefore(propertyName, ".");
722             Object propertyValue = ObjectUtils.getPropertyValue(bo, prefix);
723 
724             if (propertyValue == null) {
725                 containsNull = true;
726             }
727             else {
728                 String suffix = StringUtils.substringAfter(propertyName, ".");
729                 containsNull = containsIntermediateNull(propertyValue, suffix);
730             }
731         }
732 
733         return containsNull;
734     }
735 
736     /**
737      * Uses reflection to get the property names of the business object, then checks for the property name as a key in the passed
738      * map. If found, takes the value from the map and sets the business object property.
739      *
740      * @param bo
741      * @param fieldValues
742      * @return Cached Values from any formatting failures
743      */
744     public static Map populateBusinessObjectFromMap(BusinessObject bo, Map fieldValues) {
745         return populateBusinessObjectFromMap(bo, fieldValues, "");
746     }
747 
748     /**
749      * Uses reflection to get the property names of the business object, then checks for the property name as a key in the passed
750      * map. If found, takes the value from the map and sets the business object property.
751      *
752      * @param bo
753      * @param fieldValues
754      * @param propertyNamePrefix this value will be prepended to all property names in the returned unformattable values map
755      * @return Cached Values from any formatting failures
756      */
757     public static Map populateBusinessObjectFromMap(BusinessObject bo, Map<String, ?> fieldValues, String propertyNamePrefix) {
758         Map cachedValues = new HashMap();
759         MessageMap errorMap = GlobalVariables.getMessageMap();
760 
761         try {
762             for (Iterator<String> iter = fieldValues.keySet().iterator(); iter.hasNext();) {
763                 String propertyName = iter.next();
764 
765                 if (propertyName.endsWith(KRADConstants.CHECKBOX_PRESENT_ON_FORM_ANNOTATION)) {
766                     // since checkboxes do not post values when unchecked, this code detects whether a checkbox was unchecked, and
767                     // sets the value to false.
768                     if (StringUtils.isNotBlank((String) fieldValues.get(propertyName))) {
769                         String checkboxName = StringUtils.removeEnd(propertyName, KRADConstants.CHECKBOX_PRESENT_ON_FORM_ANNOTATION);
770                         String checkboxValue = (String) fieldValues.get(checkboxName);
771                         if (checkboxValue == null) {
772                             // didn't find a checkbox value, assume that it is unchecked
773                             if (PropertyUtils.isWriteable(bo, checkboxName)) {
774                                 Class type = ObjectUtils.easyGetPropertyType(bo, checkboxName);
775                                 if (type == Boolean.TYPE || type == Boolean.class) {
776                                     // ASSUMPTION: unchecked means false
777                                     ObjectUtils.setObjectProperty(bo, checkboxName, type, "false");
778                                 }
779                             }
780                         }
781                     }
782                     // 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
783                     // another iteration (may be before or after this iteration).
784                 }
785                 else if (PropertyUtils.isWriteable(bo, propertyName) && fieldValues.get(propertyName) != null ) {
786                     // if the field propertyName is a valid property on the bo class
787                     Class type = ObjectUtils.easyGetPropertyType(bo, propertyName);
788                     try {
789                     	Object fieldValue = fieldValues.get(propertyName);
790                         ObjectUtils.setObjectProperty(bo, propertyName, type, fieldValue);
791                     }
792                     catch (FormatException e) {
793                         cachedValues.put(propertyNamePrefix + propertyName, fieldValues.get(propertyName));
794                         errorMap.putError(propertyNamePrefix + propertyName, e.getErrorKey(), e.getErrorArgs());
795                     }
796                 }
797             }
798         }
799         catch (IllegalAccessException e) {
800             LOG.error("unable to populate business object" + e.getMessage());
801             throw new RuntimeException(e.getMessage(), e);
802         }
803         catch (InvocationTargetException e) {
804             LOG.error("unable to populate business object" + e.getMessage());
805             throw new RuntimeException(e.getMessage(), e);
806         }
807         catch (NoSuchMethodException e) {
808             LOG.error("unable to populate business object" + e.getMessage());
809             throw new RuntimeException(e.getMessage(), e);
810         }
811 
812         return cachedValues;
813     }
814 
815     /**
816      * Does prefixing and read only settings of a Field UI for display in a maintenance document.
817      *
818      * @param field - the Field object to be displayed
819      * @param keyFieldNames - Primary key property names for the business object being maintained.
820      * @param namePrefix - String to prefix Field names with.
821      * @param maintenanceAction - The maintenance action requested.
822      * @param readOnly - Indicates whether all fields should be read only.
823      * @return Field
824      */
825     public static Field fixFieldForForm(Field field, List keyFieldNames, String namePrefix, String maintenanceAction, boolean readOnly, MaintenanceDocumentRestrictions auths, String documentStatus, String documentInitiatorPrincipalId) {
826         String propertyName = field.getPropertyName();
827         // We only need to do the following processing if the field is not a sub section header
828         if (field.containsBOData()) {
829 
830             // don't prefix submit fields, must start with dispatch parameter name
831             if (!propertyName.startsWith(KRADConstants.DISPATCH_REQUEST_PARAMETER)) {
832                 // if the developer hasn't set a specific prefix use the one supplied
833                 if (field.getPropertyPrefix() == null || field.getPropertyPrefix().equals("")) {
834                     field.setPropertyName(namePrefix + propertyName);
835                 }
836                 else {
837                     field.setPropertyName(field.getPropertyPrefix() + "." + propertyName);
838                 }
839             }
840 
841             if (readOnly) {
842                 field.setReadOnly(true);
843             }
844 
845             // set keys read only for edit
846             if ( KRADConstants.MAINTENANCE_EDIT_ACTION.equals(maintenanceAction) ) {
847             	if (keyFieldNames.contains(propertyName) ) {
848 	                field.setReadOnly(true);
849 	                field.setKeyField(true);
850 	            } else if ( StringUtils.isNotBlank( field.getUniversalIdAttributeName() )
851 	            		&& keyFieldNames.contains(field.getUniversalIdAttributeName() ) ) {
852 	            	// special handling for when the principal ID is the PK field for a record
853 	            	// this causes locking down of the user ID field
854 	                field.setReadOnly(true);
855 	                field.setKeyField(true);
856 	            }
857             }
858 
859             // apply any authorization restrictions to field availability on the UI
860             applyAuthorization(field, maintenanceAction, auths, documentStatus, documentInitiatorPrincipalId);
861 
862             // if fieldConversions specified, prefix with new constant
863             if (StringUtils.isNotBlank(field.getFieldConversions())) {
864                 String fieldConversions = field.getFieldConversions();
865                 String newFieldConversions = KRADConstants.EMPTY_STRING;
866                 String[] conversions = StringUtils.split(fieldConversions, KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
867 
868                 for (int l = 0; l < conversions.length; l++) {
869                     String conversion = conversions[l];
870                     //String[] conversionPair = StringUtils.split(conversion, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR);
871                     String[] conversionPair = StringUtils.split(conversion, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR, 2);
872                     String conversionFrom = conversionPair[0];
873                     String conversionTo = conversionPair[1];
874                     conversionTo = KRADConstants.MAINTENANCE_NEW_MAINTAINABLE + conversionTo;
875                     newFieldConversions += (conversionFrom + KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR + conversionTo);
876 
877                     if (l < conversions.length) {
878                         newFieldConversions += KRADConstants.FIELD_CONVERSIONS_SEPARATOR;
879                     }
880                 }
881 
882                 field.setFieldConversions(newFieldConversions);
883             }
884 
885             // if inquiryParameters specified, prefix with new constant
886             if (StringUtils.isNotBlank(field.getInquiryParameters())) {
887                 String inquiryParameters = field.getInquiryParameters();
888                 StringBuilder newInquiryParameters = new StringBuilder();
889                 String[] parameters = StringUtils.split(inquiryParameters, KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
890 
891                 for (int l = 0; l < parameters.length; l++) {
892                     String parameter = parameters[l];
893                     //String[] parameterPair = StringUtils.split(parameter, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR);
894                     String[] parameterPair = StringUtils.split(parameter, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR, 2);
895                     String conversionFrom = parameterPair[0];
896                     String conversionTo = parameterPair[1];
897 
898                     // append the conversionFrom string, prefixed by document.newMaintainable
899                     newInquiryParameters.append(KRADConstants.MAINTENANCE_NEW_MAINTAINABLE).append(conversionFrom);
900 
901                     newInquiryParameters.append(KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR).append(conversionTo);
902 
903                     if (l < parameters.length - 1) {
904                         newInquiryParameters.append(KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
905                     }
906                 }
907 
908                 field.setInquiryParameters(newInquiryParameters.toString());
909             }
910 
911             if (Field.KUALIUSER.equals(field.getFieldType())) {
912                 // prefix the personNameAttributeName
913             	int suffixIndex = field.getPropertyName().indexOf( field.getUserIdAttributeName() );
914             	if ( suffixIndex != -1 ) {
915             		field.setPersonNameAttributeName( field.getPropertyName().substring( 0, suffixIndex ) + field.getPersonNameAttributeName() );
916             		field.setUniversalIdAttributeName( field.getPropertyName().substring( 0, suffixIndex ) + field.getUniversalIdAttributeName() );
917             	} else {
918             		field.setPersonNameAttributeName(namePrefix + field.getPersonNameAttributeName());
919             		field.setUniversalIdAttributeName(namePrefix + field.getUniversalIdAttributeName());
920             	}
921 
922                 // TODO: do we need to prefix the universalIdAttributeName in Field as well?
923             }
924 
925             // if lookupParameters specified, prefix with new constant
926             if (StringUtils.isNotBlank(field.getLookupParameters())) {
927                 String lookupParameters = field.getLookupParameters();
928                 String newLookupParameters = KRADConstants.EMPTY_STRING;
929                 String[] conversions = StringUtils.split(lookupParameters, KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
930 
931                 for (int m = 0; m < conversions.length; m++) {
932                     String conversion = conversions[m];
933                     //String[] conversionPair = StringUtils.split(conversion, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR);
934                     String[] conversionPair = StringUtils.split(conversion, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR, 2);
935                     String conversionFrom = conversionPair[0];
936                     String conversionTo = conversionPair[1];
937                     conversionFrom = KRADConstants.MAINTENANCE_NEW_MAINTAINABLE + conversionFrom;
938                     newLookupParameters += (conversionFrom + KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR + conversionTo);
939 
940                     if (m < conversions.length) {
941                         newLookupParameters += KRADConstants.FIELD_CONVERSIONS_SEPARATOR;
942                     }
943                 }
944 
945                 field.setLookupParameters(newLookupParameters);
946             }
947 
948             // CONTAINER field types have nested rows and fields that need setup for the form
949             if (Field.CONTAINER.equals(field.getFieldType())) {
950                 List containerRows = field.getContainerRows();
951                 List fixedRows = new ArrayList();
952 
953                 for (Iterator iter = containerRows.iterator(); iter.hasNext();) {
954                     Row containerRow = (Row) iter.next();
955                     List containerFields = containerRow.getFields();
956                     List fixedFields = new ArrayList();
957 
958                     for (Iterator iterator = containerFields.iterator(); iterator.hasNext();) {
959                         Field containerField = (Field) iterator.next();
960                         containerField = fixFieldForForm(containerField, keyFieldNames, namePrefix, maintenanceAction, readOnly, auths, documentStatus, documentInitiatorPrincipalId);
961                         fixedFields.add(containerField);
962                     }
963 
964                     fixedRows.add(new Row(fixedFields));
965                 }
966 
967                 field.setContainerRows(fixedRows);
968             }
969         }
970         return field;
971     }
972 
973     public static void applyAuthorization(Field field, String maintenanceAction, MaintenanceDocumentRestrictions auths, String documentStatus, String documentInitiatorPrincipalId) {
974     	String fieldName = "";
975     	FieldRestriction fieldAuth = null;
976     	Person user = GlobalVariables.getUserSession().getPerson();
977         // only apply this on the newMaintainable
978         if (field.getPropertyName().startsWith(KRADConstants.MAINTENANCE_NEW_MAINTAINABLE)) {
979             // get just the actual fieldName, with the document.newMaintainableObject, etc etc removed
980             fieldName = field.getPropertyName().substring(KRADConstants.MAINTENANCE_NEW_MAINTAINABLE.length());
981 
982             // if the field is restricted somehow
983             if (auths.hasRestriction(fieldName)) {
984                 fieldAuth = auths.getFieldRestriction(fieldName);
985                 if(KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction) || KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction)){
986                 	if((KewApiConstants.ROUTE_HEADER_SAVED_CD.equals(documentStatus) || KewApiConstants.ROUTE_HEADER_INITIATED_CD.equals(documentStatus))
987                 		&& user.getPrincipalId().equals(documentInitiatorPrincipalId)){
988 
989                 		//user should be able to see the unmark value
990                 	}else{
991                 		if(fieldAuth.isPartiallyMasked()){
992     	                	field.setSecure(true);
993     	                	fieldAuth.setShouldBeEncrypted(true);
994     	                	MaskFormatter maskFormatter = fieldAuth.getMaskFormatter();
995     	                	String displayMaskValue = maskFormatter.maskValue(field.getPropertyValue());
996     	                	field.setDisplayMaskValue(displayMaskValue);
997     	                	populateSecureField(field, field.getPropertyValue());
998                     	}
999     	                else if(fieldAuth.isMasked()){
1000     	                	field.setSecure(true);
1001     	                	fieldAuth.setShouldBeEncrypted(true);
1002     	                	MaskFormatter maskFormatter = fieldAuth.getMaskFormatter();
1003     	                	String displayMaskValue = maskFormatter.maskValue(field.getPropertyValue());
1004     	                	field.setDisplayMaskValue(displayMaskValue);
1005     	                	populateSecureField(field, field.getPropertyValue());
1006     	                }
1007                 	}
1008                 }
1009 
1010                 if (KRADConstants.MAINTENANCE_EDIT_ACTION.equals(maintenanceAction) || KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equals(maintenanceAction)) {
1011                 	// if there's existing data on the page that we're not going to clear out, then we will mask it out
1012                 	if(fieldAuth.isPartiallyMasked()){
1013 	                	field.setSecure(true);
1014 	                	fieldAuth.setShouldBeEncrypted(true);
1015 	                	MaskFormatter maskFormatter = fieldAuth.getMaskFormatter();
1016 	                	String displayMaskValue = maskFormatter.maskValue(field.getPropertyValue());
1017 	                	field.setDisplayMaskValue(displayMaskValue);
1018 	                	populateSecureField(field, field.getPropertyValue());
1019                 	}
1020 	                else if(fieldAuth.isMasked()){
1021 	                	field.setSecure(true);
1022 	                	fieldAuth.setShouldBeEncrypted(true);
1023 	                	MaskFormatter maskFormatter = fieldAuth.getMaskFormatter();
1024 	                	String displayMaskValue = maskFormatter.maskValue(field.getPropertyValue());
1025 	                	field.setDisplayMaskValue(displayMaskValue);
1026 	                	populateSecureField(field, field.getPropertyValue());
1027 	                }
1028                 }
1029 
1030                 if (Field.isInputField(field.getFieldType()) || field.getFieldType().equalsIgnoreCase(Field.CHECKBOX)) {
1031                 	// if its an editable field, allow decreasing availability to readonly or hidden
1032                     // only touch the field if the restricted type is hidden or readonly
1033                     if (fieldAuth.isReadOnly()) {
1034                         if (!field.isReadOnly() && !fieldAuth.isMasked() && !fieldAuth.isPartiallyMasked()) {
1035                             field.setReadOnly(true);
1036                         }
1037                     }
1038                     else if (fieldAuth.isHidden()) {
1039                         if (field.getFieldType() != Field.HIDDEN) {
1040                             field.setFieldType(Field.HIDDEN);
1041                         }
1042                     }
1043                 }
1044 
1045                 if(Field.BUTTON.equalsIgnoreCase(field.getFieldType()) && fieldAuth.isHidden()){
1046                 	field.setFieldType(Field.HIDDEN);
1047                 }
1048 
1049                 // if the field is readOnly, and the authorization says it should be hidden,
1050                 // then restrict it
1051                 if (field.isReadOnly() && fieldAuth.isHidden()) {
1052                     field.setFieldType(Field.HIDDEN);
1053                 }
1054 
1055             }
1056             // special check for old maintainable - need to ensure that fields hidden on the
1057             // "new" side are also hidden on the old side
1058         }
1059         else if (field.getPropertyName().startsWith(KRADConstants.MAINTENANCE_OLD_MAINTAINABLE)) {
1060             // get just the actual fieldName, with the document.oldMaintainableObject, etc etc removed
1061             fieldName = field.getPropertyName().substring(KRADConstants.MAINTENANCE_OLD_MAINTAINABLE.length());
1062             // if the field is restricted somehow
1063             if (auths.hasRestriction(fieldName)) {
1064                 fieldAuth = auths.getFieldRestriction(fieldName);
1065                 if(fieldAuth.isPartiallyMasked()){
1066                     field.setSecure(true);
1067                     MaskFormatter maskFormatter = fieldAuth.getMaskFormatter();
1068                     String displayMaskValue = maskFormatter.maskValue(field.getPropertyValue());
1069                     field.setDisplayMaskValue(displayMaskValue);
1070                     field.setPropertyValue(displayMaskValue);
1071                     populateSecureField(field, field.getPropertyValue());
1072 
1073                }
1074 
1075                if(fieldAuth.isMasked()){
1076                     field.setSecure(true);
1077                     MaskFormatter maskFormatter = fieldAuth.getMaskFormatter();
1078                     String displayMaskValue = maskFormatter.maskValue(field.getPropertyValue());
1079                     field.setDisplayMaskValue(displayMaskValue);
1080                     field.setPropertyValue(displayMaskValue);
1081                     populateSecureField(field, field.getPropertyValue());
1082                 }
1083 
1084                 if (fieldAuth.isHidden()) {
1085                     field.setFieldType(Field.HIDDEN);
1086                 }
1087             }
1088         }
1089     }
1090 
1091     /**
1092      * Merges together sections of the old maintainable and new maintainable.
1093      *
1094      * @param oldSections
1095      * @param newSections
1096      * @param keyFieldNames
1097      * @param maintenanceAction
1098      * @param readOnly
1099      * @return List of Section objects
1100      */
1101     public static List meshSections(List oldSections, List newSections, List keyFieldNames, String maintenanceAction, boolean readOnly, MaintenanceDocumentRestrictions auths, String documentStatus, String documentInitiatorPrincipalId) {
1102         List meshedSections = new ArrayList();
1103 
1104         for (int i = 0; i < newSections.size(); i++) {
1105             Section maintSection = (Section) newSections.get(i);
1106             List sectionRows = maintSection.getRows();
1107             Section oldMaintSection = (Section) oldSections.get(i);
1108             List oldSectionRows = oldMaintSection.getRows();
1109             List<Row> meshedRows = new ArrayList();
1110             meshedRows = meshRows(oldSectionRows, sectionRows, keyFieldNames, maintenanceAction, readOnly, auths, documentStatus, documentInitiatorPrincipalId);
1111             maintSection.setRows(meshedRows);
1112             if (StringUtils.isBlank(maintSection.getErrorKey())) {
1113                 maintSection.setErrorKey(MaintenanceUtils.generateErrorKeyForSection(maintSection));
1114             }
1115             meshedSections.add(maintSection);
1116         }
1117 
1118         return meshedSections;
1119     }
1120 
1121     /**
1122      * Merges together rows of an old maintainable section and new maintainable section.
1123      *
1124      * @param oldRows
1125      * @param newRows
1126      * @param keyFieldNames
1127      * @param maintenanceAction
1128      * @param readOnly
1129      * @return List of Row objects
1130      */
1131     public static List meshRows(List oldRows, List newRows, List keyFieldNames, String maintenanceAction, boolean readOnly, MaintenanceDocumentRestrictions auths, String documentStatus, String documentInitiatorPrincipalId) {
1132         List<Row> meshedRows = new ArrayList<Row>();
1133 
1134         for (int j = 0; j < newRows.size(); j++) {
1135             Row sectionRow = (Row) newRows.get(j);
1136             List rowFields = sectionRow.getFields();
1137             Row oldSectionRow = null;
1138             List oldRowFields = new ArrayList();
1139 
1140             if (null != oldRows && oldRows.size() > j) {
1141                 oldSectionRow = (Row) oldRows.get(j);
1142                 oldRowFields = oldSectionRow.getFields();
1143             }
1144 
1145             List meshedFields = meshFields(oldRowFields, rowFields, keyFieldNames, maintenanceAction, readOnly, auths, documentStatus, documentInitiatorPrincipalId);
1146             if (meshedFields.size() > 0) {
1147                 Row meshedRow = new Row(meshedFields);
1148                 if (sectionRow.isHidden()) {
1149                     meshedRow.setHidden(true);
1150                 }
1151 
1152                 meshedRows.add(meshedRow);
1153             }
1154         }
1155 
1156         return meshedRows;
1157     }
1158 
1159 
1160     /**
1161      * Merges together fields and an old maintainble row and new maintainable row, for each field call fixFieldForForm.
1162      *
1163      * @param oldFields
1164      * @param newFields
1165      * @param keyFieldNames
1166      * @param maintenanceAction
1167      * @param readOnly
1168      * @return List of Field objects
1169      */
1170     public static List meshFields(List oldFields, List newFields, List keyFieldNames, String maintenanceAction, boolean readOnly, MaintenanceDocumentRestrictions auths, String documentStatus, String documentInitiatorPrincipalId) {
1171         List meshedFields = new ArrayList();
1172 
1173         List newFieldsToMerge = new ArrayList();
1174         List oldFieldsToMerge = new ArrayList();
1175 
1176         for (int k = 0; k < newFields.size(); k++) {
1177             Field newMaintField = (Field) newFields.get(k);
1178             String propertyName = newMaintField.getPropertyName();
1179             // If this is an add button, then we have to have only this field for the entire row.
1180             if (Field.IMAGE_SUBMIT.equals(newMaintField.getFieldType())) {
1181                 meshedFields.add(newMaintField);
1182             }
1183             else if (Field.CONTAINER.equals(newMaintField.getFieldType())) {
1184                 if (oldFields.size() > k) {
1185                     Field oldMaintField = (Field) oldFields.get(k);
1186                     newMaintField = meshContainerFields(oldMaintField, newMaintField, keyFieldNames, maintenanceAction, readOnly, auths, documentStatus, documentInitiatorPrincipalId);
1187                 }
1188                 else {
1189                     newMaintField = meshContainerFields(newMaintField, newMaintField, keyFieldNames, maintenanceAction, readOnly, auths, documentStatus, documentInitiatorPrincipalId);
1190                 }
1191                 meshedFields.add(newMaintField);
1192             }
1193             else {
1194                 newMaintField = FieldUtils.fixFieldForForm(newMaintField, keyFieldNames, KRADConstants.MAINTENANCE_NEW_MAINTAINABLE, maintenanceAction, readOnly, auths, documentStatus, documentInitiatorPrincipalId);
1195                 // add old fields for edit
1196                 if (KRADConstants.MAINTENANCE_EDIT_ACTION.equals(maintenanceAction) || KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction)) {
1197                     Field oldMaintField = (Field) oldFields.get(k);
1198 
1199                     // compare values for change, and set new maintainable fields for highlighting
1200                     // no point in highlighting the hidden fields, since they won't be rendered anyways
1201                     if (!StringUtils.equalsIgnoreCase(newMaintField.getPropertyValue(), oldMaintField.getPropertyValue())
1202                             && !Field.HIDDEN.equals(newMaintField.getFieldType())) {
1203                         newMaintField.setHighlightField(true);
1204                     }
1205 
1206                     oldMaintField = FieldUtils.fixFieldForForm(oldMaintField, keyFieldNames, KRADConstants.MAINTENANCE_OLD_MAINTAINABLE, maintenanceAction, true, auths, documentStatus, documentInitiatorPrincipalId);
1207                     oldFieldsToMerge.add(oldMaintField);
1208                 }
1209 
1210                 newFieldsToMerge.add(newMaintField);
1211 
1212                 for (Iterator iter = oldFieldsToMerge.iterator(); iter.hasNext();) {
1213                     Field element = (Field) iter.next();
1214                     meshedFields.add(element);
1215                 }
1216 
1217                 for (Iterator iter = newFieldsToMerge.iterator(); iter.hasNext();) {
1218                     Field element = (Field) iter.next();
1219                     meshedFields.add(element);
1220                 }
1221             }
1222         }
1223         return meshedFields;
1224     }
1225 
1226     /**
1227      * Determines whether field level help is enabled for the field corresponding to the dataObjectClass and attribute name
1228      *
1229      * If this value is true, then the field level help will be enabled.
1230      * If false, then whether a field is enabled is determined by the value returned by {@link #isLookupFieldLevelHelpDisabled(Class, String)} and the system-wide
1231      * parameter setting.  Note that if a field is read-only, that may cause field-level help to not be rendered.
1232      *
1233      * @param businessObjectClass the looked up class
1234      * @param attributeName the attribute for the field
1235      * @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
1236      * affects the enablement of field level help
1237      */
1238     protected static boolean isLookupFieldLevelHelpEnabled(Class businessObjectClass, String attributeName) {
1239         return false;
1240     }
1241 
1242     /**
1243      * Determines whether field level help is disabled for the field corresponding to the dataObjectClass and attribute name
1244      *
1245      * If this value is true and {@link #isLookupFieldLevelHelpEnabled(Class, String)} returns false,
1246      * then the field level help will not be rendered.  If both this and {@link #isLookupFieldLevelHelpEnabled(Class, String)} return false, then the system-wide
1247      * 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.
1248      *
1249      * @param businessObjectClass the looked up class
1250      * @param attributeName the attribute for the field
1251      * @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
1252      * affects the enablement of field level help
1253      */
1254     protected static boolean isLookupFieldLevelHelpDisabled(Class businessObjectClass, String attributeName) {
1255         return false;
1256     }
1257 
1258     public static List createAndPopulateFieldsForLookup(List<String> lookupFieldAttributeList, List<String> readOnlyFieldsList, Class businessObjectClass) throws InstantiationException, IllegalAccessException {
1259         List<Field> fields = new ArrayList<Field>();
1260         BusinessObjectEntry boe = (BusinessObjectEntry) getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(businessObjectClass.getName());
1261 
1262         Map<String, Boolean> isHiddenMap = new HashMap<String, Boolean>();
1263         Map<String, Boolean> isReadOnlyMap = new HashMap<String, Boolean>();
1264 
1265         /*
1266     	 * Check if any field is hidden or read only.  This allows us to
1267     	 * set lookup criteria as hidden/readonly outside the controlDefinition.
1268     	 */
1269     	if(boe.hasLookupDefinition()){
1270     		List<FieldDefinition> fieldDefs = boe.getLookupDefinition().getLookupFields();
1271     		for(FieldDefinition field : fieldDefs){
1272 				isReadOnlyMap.put(field.getAttributeName(), Boolean.valueOf(field.isReadOnly()));
1273 				isHiddenMap.put(field.getAttributeName(), Boolean.valueOf(field.isHidden()));
1274     		}
1275     	}
1276 
1277         for( String attributeName : lookupFieldAttributeList )
1278         {
1279             Field field = FieldUtils.getPropertyField(businessObjectClass, attributeName, true);
1280 
1281             if(field.isDatePicker() && field.isRanged()) {
1282 
1283             	Field newDate = createRangeDateField(field);
1284             	fields.add(newDate);
1285             }
1286 
1287             BusinessObject newBusinessObjectInstance;
1288             if (ExternalizableBusinessObjectUtils.isExternalizableBusinessObjectInterface(businessObjectClass)) {
1289             	ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(businessObjectClass);
1290             	newBusinessObjectInstance = (BusinessObject) moduleService.createNewObjectFromExternalizableClass(businessObjectClass);
1291             }
1292             else {
1293             	newBusinessObjectInstance = (BusinessObject) businessObjectClass.newInstance();
1294             }
1295             //quickFinder is synonymous with a field-based Lookup
1296             field = LookupUtils.setFieldQuickfinder(newBusinessObjectInstance, attributeName, field, lookupFieldAttributeList);
1297             field = LookupUtils.setFieldDirectInquiry(newBusinessObjectInstance, attributeName, field);
1298 
1299             // overwrite maxLength to allow for wildcards and ranges in the select, but only if it's not a mulitselect box, because maxLength determines the # of entries
1300             if (!Field.MULTISELECT.equals(field.getFieldType())) {
1301             	field.setMaxLength(100);
1302             }
1303 
1304             // if the attrib name is "active", and BO is Inactivatable, then set the default value to Y
1305             if (attributeName.equals(KRADPropertyConstants.ACTIVE) && MutableInactivatable.class.isAssignableFrom(businessObjectClass)) {
1306             	field.setPropertyValue(KRADConstants.YES_INDICATOR_VALUE);
1307             	field.setDefaultValue(KRADConstants.YES_INDICATOR_VALUE);
1308             }
1309             // set default value
1310             String defaultValue = getBusinessObjectMetaDataService().getLookupFieldDefaultValue(businessObjectClass, attributeName);
1311             if (defaultValue != null) {
1312                 field.setPropertyValue(defaultValue);
1313                 field.setDefaultValue(defaultValue);
1314             }
1315 
1316             Class defaultValueFinderClass = getBusinessObjectMetaDataService().getLookupFieldDefaultValueFinderClass(businessObjectClass, attributeName);
1317             //getBusinessObjectMetaDataService().getLookupFieldDefaultValue(dataObjectClass, attributeName)
1318             if (defaultValueFinderClass != null) {
1319                 field.setPropertyValue(((ValueFinder) defaultValueFinderClass.newInstance()).getValue());
1320                 field.setDefaultValue(((ValueFinder) defaultValueFinderClass.newInstance()).getValue());
1321             }
1322             if ( (readOnlyFieldsList != null && readOnlyFieldsList.contains(field.getPropertyName()))
1323             		|| ( isReadOnlyMap.containsKey(field.getPropertyName()) && isReadOnlyMap.get(field.getPropertyName()).booleanValue())
1324             	) {
1325                 field.setReadOnly(true);
1326             }
1327 
1328             populateQuickfinderDefaultsForLookup(businessObjectClass, attributeName, field);
1329 
1330 			if ((isHiddenMap.containsKey(field.getPropertyName()) && isHiddenMap.get(field.getPropertyName()).booleanValue())) {
1331 				field.setFieldType(Field.HIDDEN);
1332 			}
1333             
1334             boolean triggerOnChange = getBusinessObjectDictionaryService().isLookupFieldTriggerOnChange(businessObjectClass, attributeName);
1335             field.setTriggerOnChange(triggerOnChange);
1336 
1337             field.setFieldLevelHelpEnabled(isLookupFieldLevelHelpEnabled(businessObjectClass, attributeName));
1338             field.setFieldLevelHelpDisabled(isLookupFieldLevelHelpDisabled(businessObjectClass, attributeName));
1339             
1340             fields.add(field);
1341         }
1342         return fields;
1343     }
1344 
1345 
1346 	/**
1347 	 * This method ...
1348 	 *
1349 	 * @param businessObjectClass
1350 	 * @param attributeName
1351 	 * @param field
1352 	 * @throws InstantiationException
1353 	 * @throws IllegalAccessException
1354 	 */
1355 	private static void populateQuickfinderDefaultsForLookup(
1356 			Class businessObjectClass, String attributeName, Field field)
1357 			throws InstantiationException, IllegalAccessException {
1358 		// handle quickfinderParameterString / quickfinderParameterFinderClass
1359 		String quickfinderParamString = getBusinessObjectMetaDataService().getLookupFieldQuickfinderParameterString(businessObjectClass, attributeName);
1360 		Class<? extends ValueFinder> quickfinderParameterFinderClass =
1361 			getBusinessObjectMetaDataService().getLookupFieldQuickfinderParameterStringBuilderClass(businessObjectClass, attributeName);
1362 		if (quickfinderParameterFinderClass != null) {
1363 			quickfinderParamString = quickfinderParameterFinderClass.newInstance().getValue();
1364 		}
1365 
1366 		if (!StringUtils.isEmpty(quickfinderParamString)) {
1367 			String [] params = quickfinderParamString.split(",");
1368 			if (params != null) for (String param : params) {
1369 				if (param.contains(KRADConstants.LOOKUP_PARAMETER_LITERAL_DELIMITER)) {
1370 					String[] paramChunks = param.split(KRADConstants.LOOKUP_PARAMETER_LITERAL_DELIMITER, 2);
1371 					field.appendLookupParameters(
1372 							KRADConstants.LOOKUP_PARAMETER_LITERAL_PREFIX+KRADConstants.LOOKUP_PARAMETER_LITERAL_DELIMITER+
1373 							paramChunks[1]+":"+paramChunks[0]);
1374 				}
1375 			}
1376 		}
1377 	}
1378 
1379 
1380 	/**
1381 	 * creates an extra field for date from/to ranges
1382 	 * @param field
1383 	 * @return a new date field
1384 	 */
1385 	public static Field createRangeDateField(Field field) {
1386 		Field newDate = (Field)ObjectUtils.deepCopy(field);
1387 		newDate.setFieldLabel(newDate.getFieldLabel()+" "+KRADConstants.LOOKUP_DEFAULT_RANGE_SEARCH_LOWER_BOUND_LABEL);
1388 		field.setFieldLabel(field.getFieldLabel()+" "+KRADConstants.LOOKUP_DEFAULT_RANGE_SEARCH_UPPER_BOUND_LABEL);
1389 		newDate.setPropertyName(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX+newDate.getPropertyName());
1390 		return newDate;
1391 	}
1392 
1393     private static Field meshContainerFields(Field oldMaintField, Field newMaintField, List keyFieldNames, String maintenanceAction, boolean readOnly, MaintenanceDocumentRestrictions auths, String documentStatus, String documentInitiatorPrincipalId) {
1394         List resultingRows = new ArrayList();
1395         resultingRows.addAll(meshRows(oldMaintField.getContainerRows(), newMaintField.getContainerRows(), keyFieldNames, maintenanceAction, readOnly, auths, documentStatus, documentInitiatorPrincipalId));
1396         Field resultingField = newMaintField;
1397         resultingField.setFieldType(Field.CONTAINER);
1398 
1399         // save the summary info
1400         resultingField.setContainerElementName(newMaintField.getContainerElementName());
1401         resultingField.setContainerDisplayFields(newMaintField.getContainerDisplayFields());
1402         resultingField.setNumberOfColumnsForCollection(newMaintField.getNumberOfColumnsForCollection());
1403 
1404         resultingField.setContainerRows(resultingRows);
1405         List resultingRowsList = newMaintField.getContainerRows();
1406         if (resultingRowsList.size() > 0) {
1407             List resultingFieldsList = ((Row) resultingRowsList.get(0)).getFields();
1408             if (resultingFieldsList.size() > 0) {
1409                 // todo: assign the correct propertyName to the container in the first place. For now, I'm wary of the weird usages
1410                 // of constructContainerField().
1411                 String containedFieldName = ((Field) (resultingFieldsList.get(0))).getPropertyName();
1412                 resultingField.setPropertyName(containedFieldName.substring(0, containedFieldName.lastIndexOf('.')));
1413             }
1414         }
1415         else {
1416             resultingField.setPropertyName(oldMaintField.getPropertyName());
1417         }
1418         return resultingField;
1419     }
1420 
1421     /**
1422      * This method modifies the passed in field so that it may be used to render a multiple values lookup button
1423      *
1424      * @param field this object will be modified by this method
1425      * @param parents
1426      * @param definition
1427      */
1428     static final public void modifyFieldToSupportMultipleValueLookups(Field field, String parents, MaintainableCollectionDefinition definition) {
1429         field.setMultipleValueLookedUpCollectionName(parents + definition.getName());
1430         field.setMultipleValueLookupClassName(definition.getSourceClassName().getName());
1431         field.setMultipleValueLookupClassLabel(getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(definition.getSourceClassName().getName()).getObjectLabel());
1432     }
1433 
1434     /**
1435      * Returns whether the passed in collection has been properly configured in the maint doc dictionary to support multiple value
1436      * lookups.
1437      *
1438      * @param definition
1439      * @return
1440      */
1441     static final public boolean isCollectionMultipleLookupEnabled(MaintainableCollectionDefinition definition) {
1442         return definition.getSourceClassName() != null && definition.isIncludeMultipleLookupLine();
1443     }
1444 
1445     /**
1446      * This method removes any duplicating spacing (internal or on the ends) from a String, meant to be exposed as a tag library
1447      * function.
1448      *
1449      * @param s String to remove duplicate spacing from.
1450      * @return String without duplicate spacing.
1451      */
1452     public static String scrubWhitespace(String s) {
1453         return s.replaceAll("(\\s)(\\s+)", " ");
1454     }
1455 
1456     public static List<Row> convertRemotableAttributeFields(List<RemotableAttributeField> remotableAttributeFields) {
1457         List<Row> rows = new ArrayList<Row>();
1458         for (RemotableAttributeField remotableAttributeField : remotableAttributeFields) {
1459             List<Field> fields = convertRemotableAttributeField(remotableAttributeField);
1460             // each field goes in it's own row...
1461             for (Field field : fields) {
1462                 Row row = new Row(field);
1463                 rows.add(row);
1464             }
1465         }
1466         return rows;
1467     }
1468 
1469     public static List<Field> convertRemotableAttributeField(RemotableAttributeField remotableAttributeField) {
1470         // will produce two fields in the case of a range
1471         List<Field> fields = constructFieldsForAttributeDefinition(remotableAttributeField);
1472         for (Field field : fields) {
1473             applyControlAttributes(remotableAttributeField, field);
1474             applyLookupAttributes(remotableAttributeField, field);
1475             applyWidgetAttributes(remotableAttributeField, field);
1476         }
1477         return fields;
1478     }
1479 
1480     private static List<Field> constructFieldsForAttributeDefinition(RemotableAttributeField remotableAttributeField) {
1481         List<Field> fields = new ArrayList<Field>();
1482         if (remotableAttributeField.getAttributeLookupSettings() != null && remotableAttributeField.getAttributeLookupSettings().isRanged()) {
1483             // create two fields, one for the "from" and one for the "to"
1484             AttributeLookupSettings lookupSettings = remotableAttributeField.getAttributeLookupSettings();
1485             String lowerBoundName = lookupSettings.getLowerBoundName();
1486             String upperBoundName = lookupSettings.getUpperBoundName();
1487             String lowerBoundLabel = lookupSettings.getLowerBoundLabel();
1488             String upperBoundLabel = lookupSettings.getUpperBoundLabel();
1489             if (StringUtils.isBlank(lowerBoundName)) {
1490                 lowerBoundName = "from_" + remotableAttributeField.getName();
1491             }
1492             if (StringUtils.isBlank(upperBoundName)) {
1493                 upperBoundName = "to_" + remotableAttributeField.getName();
1494             }
1495             if (StringUtils.isBlank(lowerBoundLabel)) {
1496                 lowerBoundLabel = "From " + remotableAttributeField.getLongLabel();
1497             }
1498             if (StringUtils.isBlank(upperBoundLabel)) {
1499                 upperBoundLabel = "To " + remotableAttributeField.getLongLabel();
1500             }
1501 
1502             Field lowerField = new Field(lowerBoundName, lowerBoundLabel);
1503             lowerField.setMemberOfRange(true);
1504             lowerField.setAllowInlineRange(false);
1505             lowerField.setRangeFieldInclusive(lookupSettings.isLowerBoundInclusive());
1506             fields.add(lowerField);
1507 
1508             Field upperField = new Field(upperBoundName, upperBoundLabel);
1509             upperField.setMemberOfRange(true);
1510             upperField.setAllowInlineRange(false);
1511             upperField.setRangeFieldInclusive(lookupSettings.isUpperBoundInclusive());
1512             fields.add(upperField);
1513         } else {
1514             //this ain't right....
1515             Field tempField = new Field(remotableAttributeField.getName(), remotableAttributeField.getLongLabel());
1516             tempField.setFieldLabel(remotableAttributeField.getShortLabel());
1517             fields.add(tempField);
1518         }
1519         return fields;
1520     }
1521 
1522     public static List<RemotableAttributeField> convertRowsToAttributeFields(List<Row> rows) {
1523         List<RemotableAttributeField> attributeFields = new ArrayList<RemotableAttributeField>();
1524         for (Row row : rows) {
1525             attributeFields.addAll(convertRowToAttributeFields(row));
1526         }
1527         return attributeFields;
1528     }
1529 
1530     public static List<RemotableAttributeField> convertRowToAttributeFields(Row row) {
1531         List<RemotableAttributeField> attributeFields = new ArrayList<RemotableAttributeField>();
1532         for (Field field : row.getFields()) {
1533             RemotableAttributeField remotableAttributeField = convertFieldToAttributeField(field);
1534             if (remotableAttributeField != null) {
1535                 attributeFields.add(remotableAttributeField);
1536             }
1537         }
1538         return attributeFields;
1539     }
1540 
1541     public static RemotableAttributeField convertFieldToAttributeField(Field field) {
1542         RemotableAttributeField.Builder builder = RemotableAttributeField.Builder.create(field.getPropertyName());
1543 
1544         List<RemotableAbstractWidget.Builder> widgets = new ArrayList<RemotableAbstractWidget.Builder>();
1545         builder.setDataType(DataType.valueOf(field.getFieldDataType().toUpperCase()));
1546 
1547         builder.setShortLabel(field.getFieldLabel());
1548         builder.setLongLabel(field.getMainFieldLabel());
1549         builder.setHelpSummary(field.getFieldHelpSummary());
1550         //builder.setHelpConstraint(field.)
1551         //builder.setHelpDescription();
1552         builder.setForceUpperCase(field.isUpperCase());
1553         //builder.setMinLength()
1554         builder.setMaxLength(new Integer(field.getMaxLength()));
1555         //builder.setMinValue();
1556         //builder.setMaxValue();
1557         //builder.setRegexConstraint(field.);
1558         //builder.setRegexContraintMsg();
1559         builder.setRequired(field.isFieldRequired());
1560         builder.setDefaultValues(Collections.singletonList(field.getDefaultValue()));
1561         builder.setControl(FieldUtils.constructControl(field.getFieldType(), field.getFieldValidValues()));
1562         if (field.getHasLookupable()) {
1563             builder.setAttributeLookupSettings(RemotableAttributeLookupSettings.Builder.create());
1564             RemotableQuickFinder.Builder quickfinder =
1565                     RemotableQuickFinder.Builder.create(field.getBaseLookupUrl(), field.getQuickFinderClassNameImpl());
1566             quickfinder.setFieldConversions(toMap(field.getFieldConversions()));
1567             quickfinder.setLookupParameters(toMap(field.getLookupParameters()));
1568             widgets.add(quickfinder);
1569         }
1570         if (field.isDatePicker()) {
1571             widgets.add(RemotableDatepicker.Builder.create());
1572         }
1573         if (field.isExpandedTextArea()) {
1574             widgets.add(RemotableTextExpand.Builder.create());
1575         }
1576         builder.setWidgets(widgets);
1577 
1578         return builder.build();
1579     }
1580 
1581     private static RemotableAbstractControl.Builder constructControl(String type, List<KeyValue> options) {
1582 
1583         RemotableAbstractControl.Builder control = null;
1584         Map<String, String> optionMap = new HashMap<String, String>();
1585         if (options != null) {
1586             for (KeyValue option : options) {
1587                 optionMap.put(option.getKey(), option.getValue());
1588             }
1589         }
1590         if (Field.TEXT.equals(type) || Field.DATEPICKER.equals(type)) {
1591 			control = RemotableTextInput.Builder.create();
1592         } else if (Field.TEXT_AREA.equals(type)) {
1593             control = RemotableTextarea.Builder.create();
1594 		} else if (Field.DROPDOWN.equals(type)) {
1595             control = RemotableSelect.Builder.create(optionMap);
1596         } else if (Field.CHECKBOX.equals(type)) {
1597             control = RemotableCheckboxGroup.Builder.create(optionMap);
1598 		} else if (Field.RADIO.equals(type)) {
1599             control = RemotableRadioButtonGroup.Builder.create(optionMap);
1600 		} else if (Field.HIDDEN.equals(type)) {
1601             control = RemotableHiddenInput.Builder.create();
1602 		} else if (Field.MULTIBOX.equals(type)) {
1603             RemotableSelect.Builder builder = RemotableSelect.Builder.create(optionMap);
1604             builder.setMultiple(true);
1605             control = builder;
1606         } else {
1607 		    throw new IllegalArgumentException("Illegal field type found: " + type);
1608         }
1609         return control;
1610 
1611     }
1612 
1613     private static void applyControlAttributes(RemotableAttributeField remotableField, Field field) {
1614         Control control = remotableField.getControl();
1615         String fieldType = null;
1616 
1617         if (control == null) {
1618             throw new IllegalStateException("Given attribute field with the following name has a null control: " + remotableField.getName());
1619         }
1620         if (control == null || control instanceof RemotableTextInput) {
1621             fieldType = Field.TEXT;
1622         } else if (control instanceof RemotableCheckboxGroup) {
1623             RemotableCheckboxGroup checkbox = (RemotableCheckboxGroup)control;
1624             fieldType = Field.CHECKBOX;
1625             field.setFieldValidValues(FieldUtils.convertMapToKeyValueList(checkbox.getKeyLabels()));
1626         } else if (control instanceof RemotableHiddenInput) {
1627             fieldType = Field.HIDDEN;
1628         } else if (control instanceof RemotablePasswordInput) {
1629             throw new IllegalStateException("Password control not currently supported.");
1630         } else if (control instanceof RemotableRadioButtonGroup) {
1631             fieldType = Field.RADIO;
1632             RemotableRadioButtonGroup radioControl = (RemotableRadioButtonGroup)control;
1633             field.setFieldValidValues(FieldUtils.convertMapToKeyValueList(radioControl.getKeyLabels()));
1634         } else if (control instanceof RemotableSelect) {
1635             RemotableSelect selectControl = (RemotableSelect)control;
1636 
1637             field.setFieldValidValues(FieldUtils.convertMapToKeyValueList(selectControl.getKeyLabels()));
1638             if (selectControl.isMultiple()) {
1639                 fieldType = Field.MULTISELECT;
1640             } else {
1641                 fieldType = Field.DROPDOWN;
1642             }
1643         } else if (control instanceof RemotableTextarea) {
1644             fieldType = Field.TEXT_AREA;
1645         } else {
1646             throw new IllegalArgumentException("Given control type is not supported: " + control.getClass());
1647         }
1648         field.setFieldType(fieldType);
1649     }
1650 
1651     private static List<KeyValue> convertMapToKeyValueList(Map<String, String> values) {
1652         ArrayList<KeyValue> validValues = new ArrayList<KeyValue>(values.size());
1653         for (Map.Entry<String, String> entry : values.entrySet()) {
1654             validValues.add(new ConcreteKeyValue(entry.getKey(), entry.getValue()));
1655         }
1656         return validValues;
1657     }
1658 
1659     private static void applyLookupAttributes(RemotableAttributeField remotableField, Field field) {
1660         AttributeLookupSettings lookupSettings = remotableField.getAttributeLookupSettings();
1661         if (lookupSettings != null) {
1662             field.setColumnVisible(lookupSettings.isInResults());
1663             if (!lookupSettings.isInCriteria()) {
1664                 field.setFieldType(Field.HIDDEN);
1665             }
1666         }
1667     }
1668 
1669     private static void applyWidgetAttributes(RemotableAttributeField remotableField, Field field) {
1670         Collection<? extends RemotableAbstractWidget> widgets = remotableField.getWidgets();
1671 
1672         for (RemotableAbstractWidget widget : widgets) {
1673             //yuck, do we really have to do this if else if stuff here?
1674             if (widget instanceof RemotableQuickFinder) {
1675                 field.setQuickFinderClassNameImpl(((RemotableQuickFinder)widget).getDataObjectClass());
1676                 field.setBaseLookupUrl(((RemotableQuickFinder)widget).getBaseLookupUrl());
1677                 field.setLookupParameters(((RemotableQuickFinder)widget).getLookupParameters());
1678                 field.setFieldConversions(((RemotableQuickFinder)widget).getFieldConversions());
1679             } else if (widget instanceof RemotableDatepicker) {
1680                 field.setDatePicker(true);
1681             } else if (widget instanceof RemotableTextExpand) {
1682                 field.setExpandedTextArea(true);
1683             }
1684 
1685         }
1686     }
1687 
1688     public static Column constructColumnFromAttributeField(RemotableAttributeField attributeField) {
1689         if (attributeField == null) {
1690             throw new IllegalArgumentException("attributeField was null");
1691         }
1692         DataType dataType = DataType.STRING;
1693         if (attributeField.getDataType() != null) {
1694             dataType = attributeField.getDataType();
1695         }
1696         Column column = new Column();
1697         String columnTitle = "";
1698         if (StringUtils.isBlank(attributeField.getShortLabel())) {
1699             if (StringUtils.isBlank(attributeField.getLongLabel())) {
1700                 columnTitle = attributeField.getName();
1701             } else {
1702                 columnTitle = attributeField.getLongLabel();
1703             }
1704         } else {
1705             columnTitle = attributeField.getShortLabel();
1706         }
1707         column.setColumnTitle(columnTitle);
1708         column.setSortable(Boolean.TRUE.toString());
1709         // TODO - KULRICE-5743 - maybe need this to be smaller than the actual attribute's max length?
1710         if (attributeField.getMaxLength() != null) {
1711             column.setMaxLength(attributeField.getMaxLength());
1712         }
1713         column.setPropertyName(attributeField.getName());
1714         if (attributeField.getDataType() == DataType.MARKUP) {
1715             column.setEscapeXMLValue(false);
1716         } else {
1717             column.setEscapeXMLValue(true);
1718         }
1719         column.setComparator(CellComparatorHelper.getAppropriateComparatorForPropertyClass(dataType.getClass()));
1720         column.setValueComparator(CellComparatorHelper.getAppropriateValueComparatorForPropertyClass(dataType.getClass()));
1721         column.setFormatter(FieldUtils.getFormatterForDataType(dataType));
1722         return column;
1723     }
1724 
1725     public static List<Column> constructColumnsFromAttributeFields(List<RemotableAttributeField> attributeFields) {
1726         List<Column> attributeColumns = new ArrayList<Column>();
1727         if (attributeFields != null) {
1728             for (RemotableAttributeField attributeField : attributeFields) {
1729                 attributeColumns.add(constructColumnFromAttributeField(attributeField));
1730             }
1731         }
1732         return attributeColumns;
1733     }
1734 
1735     public static Formatter getFormatterForDataType(DataType dataType) {
1736         return Formatter.getFormatter(dataType.getType());
1737     }
1738 
1739     private static Map<String, String> toMap(String s) {
1740         if (StringUtils.isBlank(s)) {
1741             return Collections.emptyMap();
1742         }
1743         final Map<String, String> map = new HashMap<String, String>();
1744         for (String string : s.split(",")) {
1745             String [] keyVal = string.split(":");
1746             map.put(keyVal[0], keyVal[1]);
1747         }
1748         return Collections.unmodifiableMap(map);
1749     }
1750 
1751     private static DataDictionaryService getDataDictionaryService() {
1752     	if (dataDictionaryService == null) {
1753     		dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
1754     	}
1755     	return dataDictionaryService;
1756     }
1757 
1758     private static BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
1759     	if (businessObjectMetaDataService == null) {
1760     		businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService();
1761     	}
1762     	return businessObjectMetaDataService;
1763     }
1764 
1765     private static BusinessObjectDictionaryService getBusinessObjectDictionaryService() {
1766     	if (businessObjectDictionaryService == null) {
1767     		businessObjectDictionaryService = KRADServiceLocatorWeb.getBusinessObjectDictionaryService();
1768     	}
1769     	return businessObjectDictionaryService;
1770     }
1771 
1772     private static KualiModuleService getKualiModuleService() {
1773     	if (kualiModuleService == null) {
1774     		kualiModuleService = KRADServiceLocatorWeb.getKualiModuleService();
1775     	}
1776     	return kualiModuleService;
1777     }
1778 }