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