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