001/** 002 * Copyright 2005-2016 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.kim.type; 017 018import com.google.common.base.Function; 019import com.google.common.collect.Lists; 020import org.apache.commons.beanutils.PropertyUtils; 021import org.apache.commons.collections.CollectionUtils; 022import org.apache.commons.lang.StringUtils; 023import org.kuali.rice.core.api.exception.RiceIllegalArgumentException; 024import org.kuali.rice.core.api.uif.RemotableAbstractWidget; 025import org.kuali.rice.core.api.uif.RemotableAttributeError; 026import org.kuali.rice.core.api.uif.RemotableAttributeField; 027import org.kuali.rice.core.api.uif.RemotableQuickFinder; 028import org.kuali.rice.core.api.util.RiceKeyConstants; 029import org.kuali.rice.core.api.util.type.TypeUtils; 030import org.kuali.rice.core.web.format.Formatter; 031import org.kuali.rice.kew.api.KewApiServiceLocator; 032import org.kuali.rice.kew.api.doctype.DocumentType; 033import org.kuali.rice.kew.api.doctype.DocumentTypeService; 034import org.kuali.rice.kim.api.services.KimApiServiceLocator; 035import org.kuali.rice.kim.api.type.KimAttributeField; 036import org.kuali.rice.kim.api.type.KimType; 037import org.kuali.rice.kim.api.type.KimTypeAttribute; 038import org.kuali.rice.kim.api.type.KimTypeInfoService; 039import org.kuali.rice.kim.framework.type.KimTypeService; 040import org.kuali.rice.kns.lookup.LookupUtils; 041import org.kuali.rice.kns.service.KNSServiceLocator; 042import org.kuali.rice.kns.util.FieldUtils; 043import org.kuali.rice.kns.web.ui.Field; 044import org.kuali.rice.krad.bo.BusinessObject; 045import org.kuali.rice.krad.comparator.StringValueComparator; 046import org.kuali.rice.krad.datadictionary.AttributeDefinition; 047import org.kuali.rice.krad.datadictionary.PrimitiveAttributeDefinition; 048import org.kuali.rice.krad.datadictionary.RelationshipDefinition; 049import org.kuali.rice.krad.service.BusinessObjectService; 050import org.kuali.rice.krad.service.DataDictionaryService; 051import org.kuali.rice.kns.service.DictionaryValidationService; 052import org.kuali.rice.krad.service.KRADServiceLocator; 053import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 054import org.kuali.rice.krad.service.ModuleService; 055import org.kuali.rice.krad.util.ErrorMessage; 056import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils; 057import org.kuali.rice.krad.util.GlobalVariables; 058import org.kuali.rice.krad.util.KRADUtils; 059import org.kuali.rice.krad.util.ObjectUtils; 060 061import java.beans.PropertyDescriptor; 062import java.util.AbstractMap; 063import java.util.ArrayList; 064import java.util.Collections; 065import java.util.Comparator; 066import java.util.HashMap; 067import java.util.Iterator; 068import java.util.List; 069import java.util.Map; 070import java.util.Set; 071import java.util.regex.Pattern; 072 073/** 074 * A base class for {@code KimTypeService} implementations which read attribute-related information from the Data 075 * Dictionary. This implementation is currently written against the KNS apis for Data Dictionary. Additionally, it 076 * supports the ability to read non-Data Dictionary attribute information from the {@link KimTypeInfoService}. 077 * 078 * @author Kuali Rice Team (rice.collab@kuali.org) 079 */ 080public class DataDictionaryTypeServiceBase implements KimTypeService { 081 082 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DataDictionaryTypeServiceBase.class); 083 private static final String ANY_CHAR_PATTERN_S = ".*"; 084 private static final Pattern ANY_CHAR_PATTERN = Pattern.compile(ANY_CHAR_PATTERN_S); 085 086 private BusinessObjectService businessObjectService; 087 private DictionaryValidationService dictionaryValidationService; 088 private DataDictionaryService dataDictionaryService; 089 private KimTypeInfoService typeInfoService; 090 private DocumentTypeService documentTypeService; 091 092 @Override 093 public String getWorkflowDocumentTypeName() { 094 return null; 095 } 096 097 @Override 098 public List<String> getWorkflowRoutingAttributes(String routeLevel) { 099 if (StringUtils.isBlank(routeLevel)) { 100 throw new RiceIllegalArgumentException("routeLevel was blank or null"); 101 } 102 103 return Collections.emptyList(); 104 } 105 106 @Override 107 public List<KimAttributeField> getAttributeDefinitions(String kimTypeId) { 108 final List<String> uniqueAttributes = getUniqueAttributes(kimTypeId); 109 110 //using map.entry as a 2-item tuple 111 final List<Map.Entry<String,KimAttributeField>> definitions = new ArrayList<Map.Entry<String,KimAttributeField>>(); 112 final KimType kimType = getTypeInfoService().getKimType(kimTypeId); 113 final String nsCode = kimType.getNamespaceCode(); 114 115 for (KimTypeAttribute typeAttribute : kimType.getAttributeDefinitions()) { 116 final KimAttributeField definition; 117 if (typeAttribute.getKimAttribute().getComponentName() == null) { 118 definition = getNonDataDictionaryAttributeDefinition(nsCode,kimTypeId,typeAttribute, uniqueAttributes); 119 } else { 120 definition = getDataDictionaryAttributeDefinition(nsCode,kimTypeId,typeAttribute, uniqueAttributes); 121 } 122 123 if (definition != null) { 124 definitions.add(new AbstractMap.SimpleEntry<String,KimAttributeField>(typeAttribute.getSortCode() != null ? typeAttribute.getSortCode() : "", definition)); 125 } 126 } 127 128 //sort by sortCode 129 Collections.sort(definitions, new Comparator<Map.Entry<String, KimAttributeField>>() { 130 @Override 131 public int compare(Map.Entry<String, KimAttributeField> o1, Map.Entry<String, KimAttributeField> o2) { 132 return o1.getKey().compareTo(o2.getKey()); 133 } 134 }); 135 136 //transform removing sortCode 137 return Collections.unmodifiableList(Lists.transform(definitions, new Function<Map.Entry<String, KimAttributeField>, KimAttributeField>() { 138 @Override 139 public KimAttributeField apply(Map.Entry<String, KimAttributeField> v) { 140 return v.getValue(); 141 } 142 })); 143 } 144 145 /** 146 * This is the default implementation. It calls into the service for each attribute to 147 * validate it there. No combination validation is done. That should be done 148 * by overriding this method. 149 */ 150 @Override 151 public List<RemotableAttributeError> validateAttributes(String kimTypeId, Map<String, String> attributes) { 152 if (StringUtils.isBlank(kimTypeId)) { 153 throw new RiceIllegalArgumentException("kimTypeId was null or blank"); 154 } 155 156 if (attributes == null) { 157 throw new RiceIllegalArgumentException("attributes was null or blank"); 158 } 159 160 final List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>(); 161 KimType kimType = getTypeInfoService().getKimType(kimTypeId); 162 163 for ( Map.Entry<String, String> entry : attributes.entrySet() ) { 164 KimTypeAttribute attr = kimType.getAttributeDefinitionByName(entry.getKey()); 165 final List<RemotableAttributeError> attributeErrors; 166 if ( attr.getKimAttribute().getComponentName() == null) { 167 attributeErrors = validateNonDataDictionaryAttribute(attr, entry.getKey(), entry.getValue()); 168 } else { 169 attributeErrors = validateDataDictionaryAttribute(attr, entry.getKey(), entry.getValue()); 170 } 171 172 if ( attributeErrors != null ) { 173 validationErrors.addAll(attributeErrors); 174 } 175 } 176 177 178 final List<RemotableAttributeError> referenceCheckErrors = validateReferencesExistAndActive(kimType, attributes, validationErrors); 179 validationErrors.addAll(referenceCheckErrors); 180 181 return Collections.unmodifiableList(validationErrors); 182 } 183 184 @Override 185 public List<RemotableAttributeError> validateAttributesAgainstExisting(String kimTypeId, Map<String, String> newAttributes, Map<String, String> oldAttributes){ 186 if (StringUtils.isBlank(kimTypeId)) { 187 throw new RiceIllegalArgumentException("kimTypeId was null or blank"); 188 } 189 190 if (newAttributes == null) { 191 throw new RiceIllegalArgumentException("newAttributes was null or blank"); 192 } 193 194 if (oldAttributes == null) { 195 throw new RiceIllegalArgumentException("oldAttributes was null or blank"); 196 } 197 return Collections.emptyList(); 198 //final List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>(); 199 //errors.addAll(validateUniqueAttributes(kimTypeId, newAttributes, oldAttributes)); 200 //return Collections.unmodifiableList(errors); 201 202 } 203 204 /** 205 * 206 * This method matches input attribute set entries and standard attribute set entries using literal string match. 207 * 208 */ 209 protected boolean performMatch(Map<String, String> inputAttributes, Map<String, String> storedAttributes) { 210 if ( storedAttributes == null || inputAttributes == null ) { 211 return true; 212 } 213 for ( Map.Entry<String, String> entry : storedAttributes.entrySet() ) { 214 if (inputAttributes.containsKey(entry.getKey()) && !StringUtils.equals(inputAttributes.get(entry.getKey()), entry.getValue()) ) { 215 return false; 216 } 217 } 218 return true; 219 } 220 221 protected Map<String, String> translateInputAttributes(Map<String, String> qualification){ 222 return qualification; 223 } 224 225 protected List<RemotableAttributeError> validateReferencesExistAndActive( KimType kimType, Map<String, String> attributes, List<RemotableAttributeError> previousValidationErrors) { 226 Map<String, BusinessObject> componentClassInstances = new HashMap<String, BusinessObject>(); 227 List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>(); 228 229 for ( String attributeName : attributes.keySet() ) { 230 KimTypeAttribute attr = kimType.getAttributeDefinitionByName(attributeName); 231 232 if (StringUtils.isNotBlank(attr.getKimAttribute().getComponentName())) { 233 if (!componentClassInstances.containsKey(attr.getKimAttribute().getComponentName())) { 234 try { 235 Class<?> componentClass = Class.forName( attr.getKimAttribute().getComponentName() ); 236 if (!BusinessObject.class.isAssignableFrom(componentClass)) { 237 LOG.warn("Class " + componentClass.getName() + " does not implement BusinessObject. Unable to perform reference existence and active validation"); 238 continue; 239 } 240 BusinessObject componentInstance = (BusinessObject) componentClass.newInstance(); 241 componentClassInstances.put(attr.getKimAttribute().getComponentName(), componentInstance); 242 } catch (Exception e) { 243 LOG.error("Unable to instantiate class for attribute: " + attributeName, e); 244 } 245 } 246 } 247 } 248 249 // now that we have instances for each component class, try to populate them with any attribute we can, assuming there were no other validation errors associated with it 250 for ( Map.Entry<String, String> entry : attributes.entrySet() ) { 251 if (!RemotableAttributeError.containsAttribute(entry.getKey(), previousValidationErrors)) { 252 for (Object componentInstance : componentClassInstances.values()) { 253 try { 254 ObjectUtils.setObjectProperty(componentInstance, entry.getKey(), entry.getValue()); 255 } catch (NoSuchMethodException e) { 256 // this is expected since not all attributes will be in all components 257 } catch (Exception e) { 258 LOG.error("Unable to set object property class: " + componentInstance.getClass().getName() + " property: " + entry.getKey(), e); 259 } 260 } 261 } 262 } 263 264 for (Map.Entry<String, BusinessObject> entry : componentClassInstances.entrySet()) { 265 List<RelationshipDefinition> relationships = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(entry.getKey()).getRelationships(); 266 if (relationships == null) { 267 relationships = getDataDictionaryService().getDataDictionary().getDataObjectEntry(entry.getKey()).getRelationships(); 268 if (relationships == null) { 269 continue; 270 } 271 } 272 273 for (RelationshipDefinition relationshipDefinition : relationships) { 274 List<PrimitiveAttributeDefinition> primitiveAttributes = relationshipDefinition.getPrimitiveAttributes(); 275 276 // this code assumes that the last defined primitiveAttribute is the attributeToHighlightOnFail 277 String attributeToHighlightOnFail = primitiveAttributes.get(primitiveAttributes.size() - 1).getSourceName(); 278 279 // TODO: will this work for user ID attributes? 280 281 if (!attributes.containsKey(attributeToHighlightOnFail)) { 282 // if the attribute to highlight wasn't passed in, don't bother validating 283 continue; 284 } 285 286 287 KimTypeAttribute attr = kimType.getAttributeDefinitionByName(attributeToHighlightOnFail); 288 if (attr != null) { 289 final String attributeDisplayLabel; 290 if (StringUtils.isNotBlank(attr.getKimAttribute().getComponentName())) { 291 attributeDisplayLabel = getDataDictionaryService().getAttributeLabel(attr.getKimAttribute().getComponentName(), attributeToHighlightOnFail); 292 } else { 293 attributeDisplayLabel = attr.getKimAttribute().getAttributeLabel(); 294 } 295 296 getDictionaryValidationService().validateReferenceExistsAndIsActive(entry.getValue(), relationshipDefinition.getObjectAttributeName(), 297 attributeToHighlightOnFail, attributeDisplayLabel); 298 } 299 List<String> extractedErrors = extractErrorsFromGlobalVariablesErrorMap(attributeToHighlightOnFail); 300 if (CollectionUtils.isNotEmpty(extractedErrors)) { 301 errors.add(RemotableAttributeError.Builder.create(attributeToHighlightOnFail, extractedErrors).build()); 302 } 303 } 304 } 305 return errors; 306 } 307 308 protected List<RemotableAttributeError> validateAttributeRequired(String kimTypeId, String objectClassName, String attributeName, Object attributeValue, String errorKey) { 309 List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>(); 310 // check if field is a required field for the business object 311 if (attributeValue == null || (attributeValue instanceof String && StringUtils.isBlank((String) attributeValue))) { 312 List<KimAttributeField> map = getAttributeDefinitions(kimTypeId); 313 KimAttributeField definition = DataDictionaryTypeServiceHelper.findAttributeField(attributeName, map); 314 315 boolean required = definition.getAttributeField().isRequired(); 316 if (required) { 317 // get label of attribute for message 318 String errorLabel = DataDictionaryTypeServiceHelper.getAttributeErrorLabel(definition); 319 errors.add(RemotableAttributeError.Builder.create(errorKey, DataDictionaryTypeServiceHelper 320 .createErrorString(RiceKeyConstants.ERROR_REQUIRED, errorLabel)).build()); 321 } 322 } 323 return errors; 324 } 325 326 protected List<RemotableAttributeError> validateDataDictionaryAttribute(String kimTypeId, String entryName, Object object, PropertyDescriptor propertyDescriptor) { 327 return validatePrimitiveFromDescriptor(kimTypeId, entryName, object, propertyDescriptor); 328 } 329 330 protected List<RemotableAttributeError> validatePrimitiveFromDescriptor(String kimTypeId, String entryName, Object object, PropertyDescriptor propertyDescriptor) { 331 List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>(); 332 // validate the primitive attributes if defined in the dictionary 333 if (null != propertyDescriptor && getDataDictionaryService().isAttributeDefined(entryName, propertyDescriptor.getName())) { 334 Object value = ObjectUtils.getPropertyValue(object, propertyDescriptor.getName()); 335 Class<?> propertyType = propertyDescriptor.getPropertyType(); 336 337 if (TypeUtils.isStringClass(propertyType) || TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) || TypeUtils.isTemporalClass(propertyType)) { 338 339 // check value format against dictionary 340 if (value != null && StringUtils.isNotBlank(value.toString())) { 341 if (!TypeUtils.isTemporalClass(propertyType)) { 342 errors.addAll(validateAttributeFormat(kimTypeId, entryName, propertyDescriptor.getName(), value.toString(), propertyDescriptor.getName())); 343 } 344 } 345 else { 346 // if it's blank, then we check whether the attribute should be required 347 errors.addAll(validateAttributeRequired(kimTypeId, entryName, propertyDescriptor.getName(), value, propertyDescriptor.getName())); 348 } 349 } 350 } 351 return errors; 352 } 353 354 protected Pattern getAttributeValidatingExpression(KimAttributeField definition) { 355 if (definition == null || StringUtils.isBlank(definition.getAttributeField().getRegexConstraint())) { 356 return ANY_CHAR_PATTERN; 357 } 358 359 return Pattern.compile(definition.getAttributeField().getRegexConstraint()); 360 } 361 362 protected Formatter getAttributeFormatter(KimAttributeField definition) { 363 if (definition.getAttributeField().getDataType() == null) { 364 return null; 365 } 366 367 return Formatter.getFormatter(definition.getAttributeField().getDataType().getType()); 368 } 369 370 371 372 protected Double getAttributeMinValue(KimAttributeField definition) { 373 return definition == null ? null : definition.getAttributeField().getMinValue(); 374 } 375 376 protected Double getAttributeMaxValue(KimAttributeField definition) { 377 return definition == null ? null : definition.getAttributeField().getMaxValue(); 378 } 379 380 protected List<RemotableAttributeError> validateAttributeFormat(String kimTypeId, String objectClassName, String attributeName, String attributeValue, String errorKey) { 381 List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>(); 382 383 List<KimAttributeField> attributeDefinitions = getAttributeDefinitions(kimTypeId); 384 KimAttributeField definition = DataDictionaryTypeServiceHelper.findAttributeField(attributeName, 385 attributeDefinitions); 386 387 String errorLabel = DataDictionaryTypeServiceHelper.getAttributeErrorLabel(definition); 388 389 if ( LOG.isDebugEnabled() ) { 390 LOG.debug("(bo, attributeName, attributeValue) = (" + objectClassName + "," + attributeName + "," + attributeValue + ")"); 391 } 392 393 if (StringUtils.isNotBlank(attributeValue)) { 394 Integer maxLength = definition.getAttributeField().getMaxLength(); 395 if ((maxLength != null) && (maxLength.intValue() < attributeValue.length())) { 396 errors.add(RemotableAttributeError.Builder.create(errorKey, DataDictionaryTypeServiceHelper 397 .createErrorString(RiceKeyConstants.ERROR_MAX_LENGTH, errorLabel, maxLength.toString())).build()); 398 return errors; 399 } 400 Pattern validationExpression = getAttributeValidatingExpression(definition); 401 if (!ANY_CHAR_PATTERN_S.equals(validationExpression.pattern())) { 402 if ( LOG.isDebugEnabled() ) { 403 LOG.debug("(bo, attributeName, validationExpression) = (" + objectClassName + "," + attributeName + "," + validationExpression + ")"); 404 } 405 406 if (!validationExpression.matcher(attributeValue).matches()) { 407 boolean isError=true; 408 final Formatter formatter = getAttributeFormatter(definition); 409 if (formatter != null) { 410 Object o = formatter.format(attributeValue); 411 isError = !validationExpression.matcher(String.valueOf(o)).matches(); 412 } 413 if (isError) { 414 errors.add(RemotableAttributeError.Builder.create(errorKey, DataDictionaryTypeServiceHelper 415 .createErrorString(definition)).build()); 416 } 417 return errors; 418 } 419 } 420 Double min = getAttributeMinValue(definition); 421 if (min != null) { 422 try { 423 if (Double.parseDouble(attributeValue) < min) { 424 errors.add(RemotableAttributeError.Builder.create(errorKey, DataDictionaryTypeServiceHelper 425 .createErrorString(RiceKeyConstants.ERROR_INCLUSIVE_MIN, errorLabel, min.toString())).build()); 426 return errors; 427 } 428 } 429 catch (NumberFormatException e) { 430 // quash; this indicates that the DD contained a min for a non-numeric attribute 431 } 432 } 433 Double max = getAttributeMaxValue(definition); 434 if (max != null) { 435 try { 436 437 if (Double.parseDouble(attributeValue) > max) { 438 errors.add(RemotableAttributeError.Builder.create(errorKey, DataDictionaryTypeServiceHelper 439 .createErrorString(RiceKeyConstants.ERROR_INCLUSIVE_MAX, errorLabel, max.toString())).build()); 440 return errors; 441 } 442 } 443 catch (NumberFormatException e) { 444 // quash; this indicates that the DD contained a max for a non-numeric attribute 445 } 446 } 447 } 448 return errors; 449 } 450 451 452 453 /* 454 * will create a list of errors in the following format: 455 * 456 * 457 * error_key:param1;param2;param3; 458 */ 459 protected List<String> extractErrorsFromGlobalVariablesErrorMap(String attributeName) { 460 Object results = GlobalVariables.getMessageMap().getErrorMessagesForProperty(attributeName); 461 List<String> errors = new ArrayList<String>(); 462 if (results instanceof String) { 463 errors.add((String)results); 464 } else if ( results != null) { 465 if (results instanceof List) { 466 List<?> errorList = (List<?>)results; 467 for (Object msg : errorList) { 468 ErrorMessage errorMessage = (ErrorMessage)msg; 469 errors.add(DataDictionaryTypeServiceHelper.createErrorString(errorMessage.getErrorKey(), 470 errorMessage.getMessageParameters())); 471 } 472 } else { 473 String [] temp = (String []) results; 474 for (String string : temp) { 475 errors.add(string); 476 } 477 } 478 } 479 GlobalVariables.getMessageMap().removeAllErrorMessagesForProperty(attributeName); 480 return errors; 481 } 482 483 protected List<RemotableAttributeError> validateNonDataDictionaryAttribute(KimTypeAttribute attr, String key, String value) { 484 return Collections.emptyList(); 485 } 486 487 protected List<RemotableAttributeError> validateDataDictionaryAttribute(KimTypeAttribute attr, String key, String value) { 488 try { 489 // create an object of the proper type per the component 490 Object componentObject = Class.forName( attr.getKimAttribute().getComponentName() ).newInstance(); 491 492 if ( attr.getKimAttribute().getAttributeName() != null ) { 493 // get the bean utils descriptor for accessing the attribute on that object 494 PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(componentObject, attr.getKimAttribute().getAttributeName()); 495 if ( propertyDescriptor != null ) { 496 // set the value on the object so that it can be checked 497 Object attributeValue = KRADUtils.hydrateAttributeValue(propertyDescriptor.getPropertyType(), value); 498 if (attributeValue == null) { 499 attributeValue = value; // not a super-awesome fallback strategy, but... 500 } 501 propertyDescriptor.getWriteMethod().invoke( componentObject, attributeValue); 502 return validateDataDictionaryAttribute(attr.getKimTypeId(), attr.getKimAttribute().getComponentName(), componentObject, propertyDescriptor); 503 } 504 } 505 } catch (Exception e) { 506 throw new KimTypeAttributeValidationException(e); 507 } 508 return Collections.emptyList(); 509 } 510 511 512 /** 513 * @param namespaceCode 514 * @param typeAttribute 515 * @return an AttributeDefinition for the given KimTypeAttribute, or null no base AttributeDefinition 516 * matches the typeAttribute parameter's attributeName. 517 */ 518 protected KimAttributeField getDataDictionaryAttributeDefinition( String namespaceCode, String kimTypeId, KimTypeAttribute typeAttribute, List<String> uniqueAttributes) { 519 520 final String componentClassName = typeAttribute.getKimAttribute().getComponentName(); 521 final String attributeName = typeAttribute.getKimAttribute().getAttributeName(); 522 final Class<? extends BusinessObject> componentClass; 523 final AttributeDefinition baseDefinition; 524 525 // try to resolve the component name - if not possible - try to pull the definition from the app mediation service 526 try { 527 if (StringUtils.isNotBlank(componentClassName)) { 528 componentClass = (Class<? extends BusinessObject>) Class.forName(componentClassName); 529 AttributeDefinition baseDefinitionTemp = 530 getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(componentClassName) 531 .getAttributeDefinition(attributeName); 532 if (baseDefinitionTemp == null) { 533 baseDefinition = getDataDictionaryService().getDataDictionary().getDataObjectEntry( 534 componentClassName).getAttributeDefinition(attributeName); 535 } else { 536 baseDefinition = baseDefinitionTemp; 537 } 538 } else { 539 baseDefinition = null; 540 componentClass = null; 541 } 542 } catch (ClassNotFoundException ex) { 543 throw new KimTypeAttributeException(ex); 544 } 545 546 if (baseDefinition == null) { 547 return null; 548 } 549 final RemotableAttributeField.Builder definition = RemotableAttributeField.Builder.create(baseDefinition.getName()); 550 551 definition.setLongLabel(baseDefinition.getLabel()); 552 definition.setShortLabel(baseDefinition.getShortLabel()); 553 definition.setMaxLength(baseDefinition.getMaxLength()); 554 555 if (baseDefinition.isRequired() != null) { 556 definition.setRequired(baseDefinition.isRequired()); 557 } else { 558 definition.setRequired(false); 559 } 560 561 if (baseDefinition.getForceUppercase() != null) { 562 definition.setForceUpperCase(baseDefinition.getForceUppercase()); 563 } 564 definition.setControl(DataDictionaryTypeServiceHelper.toRemotableAbstractControlBuilder( 565 baseDefinition)); 566 final RemotableQuickFinder.Builder qf = createQuickFinder(componentClass, attributeName); 567 if (qf != null) { 568 definition.setWidgets(Collections.<RemotableAbstractWidget.Builder>singletonList(qf)); 569 } 570 final KimAttributeField.Builder kimField = KimAttributeField.Builder.create(definition, typeAttribute.getKimAttribute().getId()); 571 572 if(uniqueAttributes!=null && uniqueAttributes.contains(definition.getName())){ 573 kimField.setUnique(true); 574 } 575 576 return kimField.build(); 577 } 578 579 private RemotableQuickFinder.Builder createQuickFinder(Class<? extends BusinessObject> componentClass, String attributeName) { 580 581 Field field = FieldUtils.getPropertyField(componentClass, attributeName, false); 582 if ( field != null ) { 583 final BusinessObject sampleComponent; 584 try { 585 sampleComponent = componentClass.newInstance(); 586 } catch(InstantiationException e) { 587 throw new KimTypeAttributeException(e); 588 } catch (IllegalAccessException e) { 589 throw new KimTypeAttributeException(e); 590 } 591 592 field = LookupUtils.setFieldQuickfinder( sampleComponent, attributeName, field, Collections.singletonList(attributeName) ); 593 if ( StringUtils.isNotBlank( field.getQuickFinderClassNameImpl() ) ) { 594 final Class<? extends BusinessObject> lookupClass; 595 try { 596 lookupClass = (Class<? extends BusinessObject>) Class.forName( field.getQuickFinderClassNameImpl() ); 597 } catch (ClassNotFoundException e) { 598 throw new KimTypeAttributeException(e); 599 } 600 601 String baseLookupUrl = LookupUtils.getBaseLookupUrl(false) + "?methodToCall=start"; 602 603 if (ExternalizableBusinessObjectUtils.isExternalizableBusinessObject(lookupClass)) { 604 ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(lookupClass); 605 if (moduleService.isExternalizableBusinessObjectLookupable(lookupClass)) { 606 baseLookupUrl = moduleService.getExternalizableBusinessObjectLookupUrl(lookupClass, Collections.<String,String>emptyMap()); 607 // XXX: I'm not proud of this: 608 baseLookupUrl = baseLookupUrl.substring(0,baseLookupUrl.indexOf("?")) + "?methodToCall=start"; 609 } 610 } 611 612 final RemotableQuickFinder.Builder builder = 613 RemotableQuickFinder.Builder.create(baseLookupUrl, lookupClass.getName()); 614 builder.setLookupParameters(toMap(field.getLookupParameters())); 615 builder.setFieldConversions(toMap(field.getFieldConversions())); 616 return builder; 617 } 618 } 619 return null; 620 } 621 622 private static Map<String, String> toMap(String s) { 623 if (StringUtils.isBlank(s)) { 624 return Collections.emptyMap(); 625 } 626 final Map<String, String> map = new HashMap<String, String>(); 627 for (String string : s.split(",")) { 628 String [] keyVal = string.split(":"); 629 map.put(keyVal[0], keyVal[1]); 630 } 631 return Collections.unmodifiableMap(map); 632 } 633 634 protected KimAttributeField getNonDataDictionaryAttributeDefinition(String namespaceCode, String kimTypeId, KimTypeAttribute typeAttribute, List<String> uniqueAttributes) { 635 RemotableAttributeField.Builder field = RemotableAttributeField.Builder.create(typeAttribute.getKimAttribute().getAttributeName()); 636 field.setLongLabel(typeAttribute.getKimAttribute().getAttributeLabel()); 637 638 //KULRICE-9143 shortLabel must be set for KIM to render attribute 639 field.setShortLabel(typeAttribute.getKimAttribute().getAttributeLabel()); 640 641 KimAttributeField.Builder definition = KimAttributeField.Builder.create(field, typeAttribute.getKimAttribute().getId()); 642 643 if(uniqueAttributes!=null && uniqueAttributes.contains(typeAttribute.getKimAttribute().getAttributeName())){ 644 definition.setUnique(true); 645 } 646 return definition.build(); 647 } 648 649 protected static final String COMMA_SEPARATOR = ", "; 650 651 protected void validateRequiredAttributesAgainstReceived(Map<String, String> receivedAttributes){ 652 // abort if type does not want the qualifiers to be checked 653 if ( !isCheckRequiredAttributes() ) { 654 return; 655 } 656 // abort if the list is empty, no attributes need to be checked 657 if ( getRequiredAttributes() == null || getRequiredAttributes().isEmpty() ) { 658 return; 659 } 660 List<String> missingAttributes = new ArrayList<String>(); 661 // if attributes are null or empty, they're all missing 662 if ( receivedAttributes == null || receivedAttributes.isEmpty() ) { 663 return; 664 } else { 665 for( String requiredAttribute : getRequiredAttributes() ) { 666 if( !receivedAttributes.containsKey(requiredAttribute) ) { 667 missingAttributes.add(requiredAttribute); 668 } 669 } 670 } 671 if(!missingAttributes.isEmpty()) { 672 StringBuilder errorMessage = new StringBuilder(); 673 Iterator<String> attribIter = missingAttributes.iterator(); 674 while ( attribIter.hasNext() ) { 675 errorMessage.append( attribIter.next() ); 676 if( attribIter.hasNext() ) { 677 errorMessage.append( COMMA_SEPARATOR ); 678 } 679 } 680 errorMessage.append( " not found in required attributes for this type." ); 681 throw new KimTypeAttributeValidationException(errorMessage.toString()); 682 } 683 } 684 685 686 @Override 687 public List<RemotableAttributeError> validateUniqueAttributes(String kimTypeId, Map<String, String> newAttributes, Map<String, String> oldAttributes) { 688 if (StringUtils.isBlank(kimTypeId)) { 689 throw new RiceIllegalArgumentException("kimTypeId was null or blank"); 690 } 691 692 if (newAttributes == null) { 693 throw new RiceIllegalArgumentException("newAttributes was null or blank"); 694 } 695 696 if (oldAttributes == null) { 697 throw new RiceIllegalArgumentException("oldAttributes was null or blank"); 698 } 699 List<String> uniqueAttributes = getUniqueAttributes(kimTypeId); 700 if(uniqueAttributes==null || uniqueAttributes.isEmpty()){ 701 return Collections.emptyList(); 702 } else{ 703 List<RemotableAttributeError> m = new ArrayList<RemotableAttributeError>(); 704 if(areAttributesEqual(uniqueAttributes, newAttributes, oldAttributes)){ 705 //add all unique attrs to error map 706 for (String a : uniqueAttributes) { 707 m.add(RemotableAttributeError.Builder.create(a, RiceKeyConstants.ERROR_DUPLICATE_ENTRY).build()); 708 } 709 710 return m; 711 } 712 } 713 return Collections.emptyList(); 714 } 715 716 protected boolean areAttributesEqual(List<String> uniqueAttributeNames, Map<String, String> aSet1, Map<String, String> aSet2){ 717 StringValueComparator comparator = StringValueComparator.getInstance(); 718 for(String uniqueAttributeName: uniqueAttributeNames){ 719 String attrVal1 = getAttributeValue(aSet1, uniqueAttributeName); 720 String attrVal2 = getAttributeValue(aSet2, uniqueAttributeName); 721 if(comparator.compare(attrVal1, attrVal2)!=0){ 722 return false; 723 } 724 } 725 return true; 726 } 727 728 protected String getAttributeValue(Map<String, String> aSet, String attributeName){ 729 if(StringUtils.isEmpty(attributeName)) { 730 return null; 731 } 732 for(Map.Entry<String, String> entry : aSet.entrySet()){ 733 if(attributeName.equals(entry.getKey())) { 734 return entry.getValue(); 735 } 736 } 737 return null; 738 } 739 740 protected List<String> getUniqueAttributes(String kimTypeId){ 741 KimType kimType = getTypeInfoService().getKimType(kimTypeId); 742 List<String> uniqueAttributes = new ArrayList<String>(); 743 if ( kimType != null ) { 744 for(KimTypeAttribute attributeDefinition: kimType.getAttributeDefinitions()){ 745 uniqueAttributes.add(attributeDefinition.getKimAttribute().getAttributeName()); 746 } 747 } else { 748 LOG.error("Unable to retrieve a KimTypeInfo for a null kimTypeId in getUniqueAttributes()"); 749 } 750 return Collections.unmodifiableList(uniqueAttributes); 751 } 752 753 @Override 754 public List<RemotableAttributeError> validateUnmodifiableAttributes(String kimTypeId, Map<String, String> originalAttributes, Map<String, String> newAttributes){ 755 if (StringUtils.isBlank(kimTypeId)) { 756 throw new RiceIllegalArgumentException("kimTypeId was null or blank"); 757 } 758 759 if (newAttributes == null) { 760 throw new RiceIllegalArgumentException("newAttributes was null or blank"); 761 } 762 763 if (originalAttributes == null) { 764 throw new RiceIllegalArgumentException("oldAttributes was null or blank"); 765 } 766 List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>(); 767 KimType kimType = getTypeInfoService().getKimType(kimTypeId); 768 List<String> uniqueAttributes = getUniqueAttributes(kimTypeId); 769 for(String attributeNameKey: uniqueAttributes){ 770 KimTypeAttribute attr = kimType.getAttributeDefinitionByName(attributeNameKey); 771 String mainAttributeValue = getAttributeValue(originalAttributes, attributeNameKey); 772 String delegationAttributeValue = getAttributeValue(newAttributes, attributeNameKey); 773 774 if(!StringUtils.equals(mainAttributeValue, delegationAttributeValue)){ 775 validationErrors.add(RemotableAttributeError.Builder.create(attributeNameKey, DataDictionaryTypeServiceHelper 776 .createErrorString(RiceKeyConstants.ERROR_CANT_BE_MODIFIED, 777 dataDictionaryService.getAttributeLabel(attr.getKimAttribute().getComponentName(), 778 attributeNameKey))).build()); 779 } 780 } 781 return validationErrors; 782 } 783 784 protected List<String> getRequiredAttributes() { 785 return Collections.emptyList(); 786 } 787 788 protected boolean isCheckRequiredAttributes() { 789 return false; 790 } 791 792 protected String getClosestParentDocumentTypeName( 793 DocumentType documentType, 794 Set<String> potentialParentDocumentTypeNames) { 795 if ( potentialParentDocumentTypeNames == null || documentType == null ) { 796 return null; 797 } 798 if (potentialParentDocumentTypeNames.contains(documentType.getName())) { 799 return documentType.getName(); 800 } 801 if ((documentType.getParentId() == null) 802 || documentType.getParentId().equals( 803 documentType.getId())) { 804 return null; 805 } 806 return getClosestParentDocumentTypeName(getDocumentTypeService().getDocumentTypeById(documentType 807 .getParentId()), potentialParentDocumentTypeNames); 808 } 809 810 protected static class KimTypeAttributeValidationException extends RuntimeException { 811 812 protected KimTypeAttributeValidationException(String message) { 813 super( message ); 814 } 815 816 protected KimTypeAttributeValidationException(Throwable cause) { 817 super( cause ); 818 } 819 820 private static final long serialVersionUID = 8220618846321607801L; 821 822 } 823 824 protected static class KimTypeAttributeException extends RuntimeException { 825 826 protected KimTypeAttributeException(String message) { 827 super( message ); 828 } 829 830 protected KimTypeAttributeException(Throwable cause) { 831 super( cause ); 832 } 833 834 private static final long serialVersionUID = 8220618846321607801L; 835 836 } 837 838 protected KimTypeInfoService getTypeInfoService() { 839 if ( typeInfoService == null ) { 840 typeInfoService = KimApiServiceLocator.getKimTypeInfoService(); 841 } 842 return typeInfoService; 843 } 844 845 protected BusinessObjectService getBusinessObjectService() { 846 if ( businessObjectService == null ) { 847 businessObjectService = KRADServiceLocator.getBusinessObjectService(); 848 } 849 return businessObjectService; 850 } 851 852 protected DictionaryValidationService getDictionaryValidationService() { 853 if ( dictionaryValidationService == null ) { 854 dictionaryValidationService = KNSServiceLocator.getKNSDictionaryValidationService(); 855 } 856 return dictionaryValidationService; 857 } 858 859 protected DataDictionaryService getDataDictionaryService() { 860 if ( dataDictionaryService == null ) { 861 dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService(); 862 } 863 return this.dataDictionaryService; 864 } 865 866 867 protected DocumentTypeService getDocumentTypeService() { 868 if ( documentTypeService == null ) { 869 documentTypeService = KewApiServiceLocator.getDocumentTypeService(); 870 } 871 return this.documentTypeService; 872 } 873}