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