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