View Javadoc

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