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 }