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