001 /**
002 * Copyright 2005-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.kns.maintenance.rules;
017
018 import org.apache.commons.collections.CollectionUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.kuali.rice.core.api.CoreApiServiceLocator;
021 import org.kuali.rice.core.api.config.property.ConfigurationService;
022 import org.kuali.rice.core.api.datetime.DateTimeService;
023 import org.kuali.rice.core.api.mo.common.active.MutableInactivatable;
024 import org.kuali.rice.core.api.util.RiceKeyConstants;
025 import org.kuali.rice.core.web.format.Formatter;
026 import org.kuali.rice.kew.api.WorkflowDocument;
027 import org.kuali.rice.kim.api.identity.PersonService;
028 import org.kuali.rice.kim.api.role.RoleService;
029 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
030 import org.kuali.rice.kns.document.MaintenanceDocument;
031 import org.kuali.rice.kns.document.authorization.MaintenanceDocumentAuthorizer;
032 import org.kuali.rice.kns.maintenance.Maintainable;
033 import org.kuali.rice.kns.rule.AddCollectionLineRule;
034 import org.kuali.rice.kns.rules.DocumentRuleBase;
035 import org.kuali.rice.kns.rules.MaintenanceDocumentRule;
036 import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
037 import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
038 import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
039 import org.kuali.rice.kns.service.DictionaryValidationService;
040 import org.kuali.rice.kns.service.DocumentHelperService;
041 import org.kuali.rice.kns.service.KNSServiceLocator;
042 import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
043 import org.kuali.rice.kns.util.RouteToCompletionUtil;
044 import org.kuali.rice.krad.bo.BusinessObject;
045 import org.kuali.rice.krad.bo.GlobalBusinessObject;
046 import org.kuali.rice.krad.bo.PersistableBusinessObject;
047 import org.kuali.rice.krad.datadictionary.InactivationBlockingMetadata;
048 import org.kuali.rice.krad.document.Document;
049 import org.kuali.rice.krad.exception.ValidationException;
050 import org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent;
051 import org.kuali.rice.krad.service.BusinessObjectService;
052 import org.kuali.rice.krad.service.DataDictionaryService;
053 import org.kuali.rice.krad.service.DataObjectMetaDataService;
054 import org.kuali.rice.krad.service.InactivationBlockingDetectionService;
055 import org.kuali.rice.krad.service.KRADServiceLocator;
056 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
057 import org.kuali.rice.krad.service.PersistenceStructureService;
058 import org.kuali.rice.krad.util.ErrorMessage;
059 import org.kuali.rice.krad.util.ForeignKeyFieldsPopulationState;
060 import org.kuali.rice.krad.util.GlobalVariables;
061 import org.kuali.rice.krad.util.KRADConstants;
062 import org.kuali.rice.krad.util.KRADPropertyConstants;
063 import org.kuali.rice.krad.util.MessageMap;
064 import org.kuali.rice.krad.util.ObjectUtils;
065 import org.kuali.rice.krad.util.UrlFactory;
066 import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
067 import org.springframework.util.AutoPopulatingList;
068
069 import java.security.GeneralSecurityException;
070 import java.util.ArrayList;
071 import java.util.Collection;
072 import java.util.Iterator;
073 import java.util.List;
074 import java.util.Map;
075 import java.util.Properties;
076 import java.util.Set;
077
078 /**
079 * Contains all of the business rules that are common to all maintenance documents
080 *
081 * @deprecated use KRAD
082 *
083 * @author Kuali Rice Team (rice.collab@kuali.org)
084 */
085 @Deprecated
086 public class MaintenanceDocumentRuleBase extends DocumentRuleBase implements MaintenanceDocumentRule, AddCollectionLineRule {
087 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceDocumentRuleBase.class);
088
089 // these two constants are used to correctly prefix errors added to
090 // the global errors
091 public static final String MAINTAINABLE_ERROR_PREFIX = KRADConstants.MAINTENANCE_NEW_MAINTAINABLE;
092 public static final String DOCUMENT_ERROR_PREFIX = "document.";
093 public static final String MAINTAINABLE_ERROR_PATH = DOCUMENT_ERROR_PREFIX + "newMaintainableObject";
094
095 protected PersistenceStructureService persistenceStructureService;
096 protected DataDictionaryService ddService;
097 protected DocumentHelperService documentHelperService;
098 protected BusinessObjectService boService;
099 protected DictionaryValidationService dictionaryValidationService;
100 protected ConfigurationService configService;
101 protected MaintenanceDocumentDictionaryService maintDocDictionaryService;
102 protected WorkflowDocumentService workflowDocumentService;
103 protected PersonService personService;
104 protected RoleService roleService;
105 protected DataObjectMetaDataService dataObjectMetaDataService;
106 protected BusinessObjectAuthorizationService businessObjectAuthorizationService;
107 protected BusinessObjectMetaDataService businessObjectMetaDataService;
108 protected BusinessObjectDictionaryService boDictionaryService;
109
110 private Object oldBo;
111 private Object newBo;
112 private Class boClass;
113
114 protected List priorErrorPath;
115
116 /**
117 * Default constructor a MaintenanceDocumentRuleBase.java.
118 */
119 public MaintenanceDocumentRuleBase() {
120
121 priorErrorPath = new ArrayList();
122
123 // Pseudo-inject some services.
124 //
125 // This approach is being used to make it simpler to convert the Rule classes
126 // to spring-managed with these services injected by Spring at some later date.
127 // When this happens, just remove these calls to the setters with
128 // SpringServiceLocator, and configure the bean defs for spring.
129 try {
130 this.setPersistenceStructureService(KNSServiceLocator.getPersistenceStructureService());
131 this.setDdService(KRADServiceLocatorWeb.getDataDictionaryService());
132 this.setBusinessObjectMetaDataService(KNSServiceLocator.getBusinessObjectMetaDataService());
133 this.setBoService(KNSServiceLocator.getBusinessObjectService());
134 this.setBoDictionaryService(KNSServiceLocator.getBusinessObjectDictionaryService());
135 this.setDictionaryValidationService(super.getDictionaryValidationService());
136 this.setConfigService(CoreApiServiceLocator.getKualiConfigurationService());
137 this.setDocumentHelperService(KNSServiceLocator.getDocumentHelperService());
138 this.setMaintDocDictionaryService(KNSServiceLocator.getMaintenanceDocumentDictionaryService());
139 this.setWorkflowDocumentService(KRADServiceLocatorWeb.getWorkflowDocumentService());
140 this.setPersonService(KimApiServiceLocator.getPersonService());
141 this.setBusinessObjectAuthorizationService(KNSServiceLocator.getBusinessObjectAuthorizationService());
142 } catch (Exception ex) {
143 // do nothing, avoid blowing up if called prior to spring initialization
144 }
145 }
146
147 /**
148 * @see org.kuali.rice.krad.rules.MaintenanceDocumentRule#processSaveDocument(org.kuali.rice.krad.document.Document)
149 */
150 @Override
151 public boolean processSaveDocument(Document document) {
152
153 MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document;
154
155 // remove all items from the errorPath temporarily (because it may not
156 // be what we expect, or what we need)
157 clearErrorPath();
158
159 // setup convenience pointers to the old & new bo
160 setupBaseConvenienceObjects(maintenanceDocument);
161
162 // the document must be in a valid state for saving. this does not include business
163 // rules, but just enough testing that the document is populated and in a valid state
164 // to not cause exceptions when saved. if this passes, then the save will always occur,
165 // regardless of business rules.
166 if (!isDocumentValidForSave(maintenanceDocument)) {
167 resumeErrorPath();
168 return false;
169 }
170
171 // apply rules that are specific to the class of the maintenance document
172 // (if implemented). this will always succeed if not overloaded by the
173 // subclass
174 processCustomSaveDocumentBusinessRules(maintenanceDocument);
175
176 // return the original set of items to the errorPath
177 resumeErrorPath();
178
179 // return the original set of items to the errorPath, to ensure no impact
180 // on other upstream or downstream items that rely on the errorPath
181 return true;
182 }
183
184 /**
185 * @see org.kuali.rice.krad.rules.MaintenanceDocumentRule#processRouteDocument(org.kuali.rice.krad.document.Document)
186 */
187 @Override
188 public boolean processRouteDocument(Document document) {
189 LOG.info("processRouteDocument called");
190
191 MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document;
192
193 boolean completeRequestPending = RouteToCompletionUtil.checkIfAtleastOneAdHocCompleteRequestExist(maintenanceDocument);
194
195 // Validate the document if the header is valid and no pending completion requests
196 if (completeRequestPending) {
197 return true;
198 }
199
200 // get the documentAuthorizer for this document
201 MaintenanceDocumentAuthorizer documentAuthorizer =
202 (MaintenanceDocumentAuthorizer) getDocumentHelperService().getDocumentAuthorizer(document);
203
204 // remove all items from the errorPath temporarily (because it may not
205 // be what we expect, or what we need)
206 clearErrorPath();
207
208 // setup convenience pointers to the old & new bo
209 setupBaseConvenienceObjects(maintenanceDocument);
210
211 // apply rules that are common across all maintenance documents, regardless of class
212 processGlobalSaveDocumentBusinessRules(maintenanceDocument);
213
214 // from here on, it is in a default-success mode, and will route unless one of the
215 // business rules stop it.
216 boolean success = true;
217
218 WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument();
219 if (workflowDocument.isInitiated() || workflowDocument.isSaved()) {
220 success &= documentAuthorizer
221 .canCreateOrMaintain((MaintenanceDocument) document, GlobalVariables.getUserSession().getPerson());
222 if (success == false) {
223 GlobalVariables.getMessageMap()
224 .putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.AUTHORIZATION_ERROR_DOCUMENT,
225 new String[]{GlobalVariables.getUserSession().getPerson().getPrincipalName(),
226 "Create/Maintain",
227 this.getMaintDocDictionaryService().getDocumentTypeName(newBo.getClass())});
228 }
229 }
230 // apply rules that are common across all maintenance documents, regardless of class
231 success &= processGlobalRouteDocumentBusinessRules(maintenanceDocument);
232
233 // apply rules that are specific to the class of the maintenance document
234 // (if implemented). this will always succeed if not overloaded by the
235 // subclass
236 success &= processCustomRouteDocumentBusinessRules(maintenanceDocument);
237
238 success &= processInactivationBlockChecking(maintenanceDocument);
239
240 // return the original set of items to the errorPath, to ensure no impact
241 // on other upstream or downstream items that rely on the errorPath
242 resumeErrorPath();
243
244 return success;
245 }
246
247 /**
248 * Determines whether a document is inactivating the record being maintained
249 *
250 * @param maintenanceDocument
251 * @return true iff the document is inactivating the business object; false otherwise
252 */
253 protected boolean isDocumentInactivatingBusinessObject(MaintenanceDocument maintenanceDocument) {
254 if (maintenanceDocument.isEdit()) {
255 Class boClass = maintenanceDocument.getNewMaintainableObject().getDataObjectClass();
256 // we can only be inactivating a business object if we're editing it
257 if (boClass != null && MutableInactivatable.class.isAssignableFrom(boClass)) {
258 MutableInactivatable oldInactivateableBO = (MutableInactivatable) oldBo;
259 MutableInactivatable newInactivateableBO = (MutableInactivatable) newBo;
260
261 return oldInactivateableBO.isActive() && !newInactivateableBO.isActive();
262 }
263 }
264 return false;
265 }
266
267 /**
268 * Determines whether this document has been inactivation blocked
269 *
270 * @param maintenanceDocument
271 * @return true iff there is NOTHING that blocks this record
272 */
273 protected boolean processInactivationBlockChecking(MaintenanceDocument maintenanceDocument) {
274 if (isDocumentInactivatingBusinessObject(maintenanceDocument)) {
275 Class boClass = maintenanceDocument.getNewMaintainableObject().getDataObjectClass();
276 Set<InactivationBlockingMetadata> inactivationBlockingMetadatas =
277 ddService.getAllInactivationBlockingDefinitions(boClass);
278
279 if (inactivationBlockingMetadatas != null) {
280 for (InactivationBlockingMetadata inactivationBlockingMetadata : inactivationBlockingMetadatas) {
281 // for the purposes of maint doc validation, we only need to look for the first blocking record
282
283 // we found a blocking record, so we return false
284 if (!processInactivationBlockChecking(maintenanceDocument, inactivationBlockingMetadata)) {
285 return false;
286 }
287 }
288 }
289 }
290 return true;
291 }
292
293 /**
294 * Given a InactivationBlockingMetadata, which represents a relationship that may block inactivation of a BO, it
295 * determines whether there
296 * is a record that violates the blocking definition
297 *
298 * @param maintenanceDocument
299 * @param inactivationBlockingMetadata
300 * @return true iff, based on the InactivationBlockingMetadata, the maintenance document should be allowed to route
301 */
302 protected boolean processInactivationBlockChecking(MaintenanceDocument maintenanceDocument,
303 InactivationBlockingMetadata inactivationBlockingMetadata) {
304 if (newBo instanceof PersistableBusinessObject) {
305 String inactivationBlockingDetectionServiceBeanName =
306 inactivationBlockingMetadata.getInactivationBlockingDetectionServiceBeanName();
307 if (StringUtils.isBlank(inactivationBlockingDetectionServiceBeanName)) {
308 inactivationBlockingDetectionServiceBeanName =
309 KRADServiceLocatorWeb.DEFAULT_INACTIVATION_BLOCKING_DETECTION_SERVICE;
310 }
311 InactivationBlockingDetectionService inactivationBlockingDetectionService = KRADServiceLocatorWeb
312 .getInactivationBlockingDetectionService(inactivationBlockingDetectionServiceBeanName);
313
314 boolean foundBlockingRecord = inactivationBlockingDetectionService
315 .hasABlockingRecord((PersistableBusinessObject) newBo, inactivationBlockingMetadata);
316
317 if (foundBlockingRecord) {
318 putInactivationBlockingErrorOnPage(maintenanceDocument, inactivationBlockingMetadata);
319 }
320
321 return !foundBlockingRecord;
322 }
323
324 return true;
325 }
326
327 /**
328 * If there is a violation of an InactivationBlockingMetadata, it prints out an appropriate error into the error
329 * map
330 *
331 * @param document
332 * @param inactivationBlockingMetadata
333 */
334 protected void putInactivationBlockingErrorOnPage(MaintenanceDocument document,
335 InactivationBlockingMetadata inactivationBlockingMetadata) {
336 if (!persistenceStructureService.hasPrimaryKeyFieldValues(newBo)) {
337 throw new RuntimeException("Maintenance document did not have all primary key values filled in.");
338 }
339 Properties parameters = new Properties();
340 parameters.put(KRADConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE,
341 inactivationBlockingMetadata.getBlockedBusinessObjectClass().getName());
342 parameters
343 .put(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.METHOD_DISPLAY_ALL_INACTIVATION_BLOCKERS);
344
345 List keys = new ArrayList();
346 if (getPersistenceStructureService().isPersistable(newBo.getClass())) {
347 keys = getPersistenceStructureService().listPrimaryKeyFieldNames(newBo.getClass());
348 }
349
350 // build key value url parameters used to retrieve the business object
351 String keyName = null;
352 for (Iterator iter = keys.iterator(); iter.hasNext(); ) {
353 keyName = (String) iter.next();
354
355 Object keyValue = null;
356 if (keyName != null) {
357 keyValue = ObjectUtils.getPropertyValue(newBo, keyName);
358 }
359
360 if (keyValue == null) {
361 keyValue = "";
362 } else if (keyValue instanceof java.sql.Date) { //format the date for passing in url
363 if (Formatter.findFormatter(keyValue.getClass()) != null) {
364 Formatter formatter = Formatter.getFormatter(keyValue.getClass());
365 keyValue = (String) formatter.format(keyValue);
366 }
367 } else {
368 keyValue = keyValue.toString();
369 }
370
371 // Encrypt value if it is a secure field
372 if (businessObjectAuthorizationService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(
373 inactivationBlockingMetadata.getBlockedBusinessObjectClass(), keyName)) {
374 try {
375 if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
376 keyValue = CoreApiServiceLocator.getEncryptionService().encrypt(keyValue);
377 }
378 } catch (GeneralSecurityException e) {
379 LOG.error("Exception while trying to encrypted value for inquiry framework.", e);
380 throw new RuntimeException(e);
381 }
382 }
383
384 parameters.put(keyName, keyValue);
385 }
386
387 String blockingUrl =
388 UrlFactory.parameterizeUrl(KRADConstants.DISPLAY_ALL_INACTIVATION_BLOCKERS_ACTION, parameters);
389
390 // post an error about the locked document
391 GlobalVariables.getMessageMap()
392 .putError(KRADConstants.GLOBAL_ERRORS, RiceKeyConstants.ERROR_INACTIVATION_BLOCKED, blockingUrl);
393 }
394
395 /**
396 * @see org.kuali.rice.krad.rules.MaintenanceDocumentRule#processApproveDocument(org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent)
397 */
398 @Override
399 public boolean processApproveDocument(ApproveDocumentEvent approveEvent) {
400
401 MaintenanceDocument maintenanceDocument = (MaintenanceDocument) approveEvent.getDocument();
402
403 // remove all items from the errorPath temporarily (because it may not
404 // be what we expect, or what we need)
405 clearErrorPath();
406
407 // setup convenience pointers to the old & new bo
408 setupBaseConvenienceObjects(maintenanceDocument);
409
410 // apply rules that are common across all maintenance documents, regardless of class
411 processGlobalSaveDocumentBusinessRules(maintenanceDocument);
412
413 // from here on, it is in a default-success mode, and will approve unless one of the
414 // business rules stop it.
415 boolean success = true;
416
417 // apply rules that are common across all maintenance documents, regardless of class
418 success &= processGlobalApproveDocumentBusinessRules(maintenanceDocument);
419
420 // apply rules that are specific to the class of the maintenance document
421 // (if implemented). this will always succeed if not overloaded by the
422 // subclass
423 success &= processCustomApproveDocumentBusinessRules(maintenanceDocument);
424
425 // return the original set of items to the errorPath, to ensure no impact
426 // on other upstream or downstream items that rely on the errorPath
427 resumeErrorPath();
428
429 return success;
430 }
431
432 /**
433 * This method is a convenience method to easily add a Document level error (ie, one not tied to a specific field,
434 * but
435 * applicable to the whole document).
436 *
437 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
438 */
439 protected void putGlobalError(String errorConstant) {
440 if (!errorAlreadyExists(KRADConstants.DOCUMENT_ERRORS, errorConstant)) {
441 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRADConstants.DOCUMENT_ERRORS, errorConstant);
442 }
443 }
444
445 /**
446 * This method is a convenience method to easily add a Document level error (ie, one not tied to a specific field,
447 * but
448 * applicable to the whole document).
449 *
450 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
451 * @param parameter - Replacement value for part of the error message.
452 */
453 protected void putGlobalError(String errorConstant, String parameter) {
454 if (!errorAlreadyExists(KRADConstants.DOCUMENT_ERRORS, errorConstant)) {
455 GlobalVariables.getMessageMap()
456 .putErrorWithoutFullErrorPath(KRADConstants.DOCUMENT_ERRORS, errorConstant, parameter);
457 }
458 }
459
460 /**
461 * This method is a convenience method to easily add a Document level error (ie, one not tied to a specific field,
462 * but
463 * applicable to the whole document).
464 *
465 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
466 * @param parameters - Array of replacement values for part of the error message.
467 */
468 protected void putGlobalError(String errorConstant, String[] parameters) {
469 if (!errorAlreadyExists(KRADConstants.DOCUMENT_ERRORS, errorConstant)) {
470 GlobalVariables.getMessageMap()
471 .putErrorWithoutFullErrorPath(KRADConstants.DOCUMENT_ERRORS, errorConstant, parameters);
472 }
473 }
474
475 /**
476 * This method is a convenience method to add a property-specific error to the global errors list. This method makes
477 * sure that
478 * the correct prefix is added to the property name so that it will display correctly on maintenance documents.
479 *
480 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as
481 * errored in
482 * the UI.
483 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
484 */
485 protected void putFieldError(String propertyName, String errorConstant) {
486 if (!errorAlreadyExists(MAINTAINABLE_ERROR_PREFIX + propertyName, errorConstant)) {
487 GlobalVariables.getMessageMap()
488 .putErrorWithoutFullErrorPath(MAINTAINABLE_ERROR_PREFIX + propertyName, errorConstant);
489 }
490 }
491
492 /**
493 * This method is a convenience method to add a property-specific error to the global errors list. This method makes
494 * sure that
495 * the correct prefix is added to the property name so that it will display correctly on maintenance documents.
496 *
497 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as
498 * errored in
499 * the UI.
500 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
501 * @param parameter - Single parameter value that can be used in the message so that you can display specific values
502 * to the
503 * user.
504 */
505 protected void putFieldError(String propertyName, String errorConstant, String parameter) {
506 if (!errorAlreadyExists(MAINTAINABLE_ERROR_PREFIX + propertyName, errorConstant)) {
507 GlobalVariables.getMessageMap()
508 .putErrorWithoutFullErrorPath(MAINTAINABLE_ERROR_PREFIX + propertyName, errorConstant, parameter);
509 }
510 }
511
512 /**
513 * This method is a convenience method to add a property-specific error to the global errors list. This method makes
514 * sure that
515 * the correct prefix is added to the property name so that it will display correctly on maintenance documents.
516 *
517 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as
518 * errored in
519 * the UI.
520 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
521 * @param parameters - Array of strings holding values that can be used in the message so that you can display
522 * specific values
523 * to the user.
524 */
525 protected void putFieldError(String propertyName, String errorConstant, String[] parameters) {
526 if (!errorAlreadyExists(MAINTAINABLE_ERROR_PREFIX + propertyName, errorConstant)) {
527 GlobalVariables.getMessageMap()
528 .putErrorWithoutFullErrorPath(MAINTAINABLE_ERROR_PREFIX + propertyName, errorConstant, parameters);
529 }
530 }
531
532 /**
533 * Adds a property-specific error to the global errors list, with the DD short label as the single argument.
534 *
535 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as
536 * errored in
537 * the UI.
538 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
539 */
540 protected void putFieldErrorWithShortLabel(String propertyName, String errorConstant) {
541 String shortLabel = ddService.getAttributeShortLabel(boClass, propertyName);
542 putFieldError(propertyName, errorConstant, shortLabel);
543 }
544
545 /**
546 * This method is a convenience method to add a property-specific document error to the global errors list. This
547 * method makes
548 * sure that the correct prefix is added to the property name so that it will display correctly on maintenance
549 * documents.
550 *
551 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as
552 * errored in
553 * the UI.
554 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
555 * @param parameter - Single parameter value that can be used in the message so that you can display specific values
556 * to the
557 * user.
558 */
559 protected void putDocumentError(String propertyName, String errorConstant, String parameter) {
560 if (!errorAlreadyExists(DOCUMENT_ERROR_PREFIX + propertyName, errorConstant)) {
561 GlobalVariables.getMessageMap().putError(DOCUMENT_ERROR_PREFIX + propertyName, errorConstant, parameter);
562 }
563 }
564
565 /**
566 * This method is a convenience method to add a property-specific document error to the global errors list. This
567 * method makes
568 * sure that the correct prefix is added to the property name so that it will display correctly on maintenance
569 * documents.
570 *
571 * @param propertyName - Property name of the element that is associated with the error. Used to mark the field as
572 * errored in
573 * the UI.
574 * @param errorConstant - Error Constant that can be mapped to a resource for the actual text message.
575 * @param parameters - Array of String parameters that can be used in the message so that you can display specific
576 * values to the
577 * user.
578 */
579 protected void putDocumentError(String propertyName, String errorConstant, String[] parameters) {
580 GlobalVariables.getMessageMap().putError(DOCUMENT_ERROR_PREFIX + propertyName, errorConstant, parameters);
581 }
582
583 /**
584 * Convenience method to determine whether the field already has the message indicated.
585 *
586 * This is useful if you want to suppress duplicate error messages on the same field.
587 *
588 * @param propertyName - propertyName you want to test on
589 * @param errorConstant - errorConstant you want to test
590 * @return returns True if the propertyName indicated already has the errorConstant indicated, false otherwise
591 */
592 protected boolean errorAlreadyExists(String propertyName, String errorConstant) {
593
594 if (GlobalVariables.getMessageMap().fieldHasMessage(propertyName, errorConstant)) {
595 return true;
596 } else {
597 return false;
598 }
599 }
600
601 /**
602 * This method specifically doesn't put any prefixes before the error so that the developer can do things specific
603 * to the
604 * globals errors (like newDelegateChangeDocument errors)
605 *
606 * @param propertyName
607 * @param errorConstant
608 */
609 protected void putGlobalsError(String propertyName, String errorConstant) {
610 if (!errorAlreadyExists(propertyName, errorConstant)) {
611 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(propertyName, errorConstant);
612 }
613 }
614
615 /**
616 * This method specifically doesn't put any prefixes before the error so that the developer can do things specific
617 * to the
618 * globals errors (like newDelegateChangeDocument errors)
619 *
620 * @param propertyName
621 * @param errorConstant
622 * @param parameter
623 */
624 protected void putGlobalsError(String propertyName, String errorConstant, String parameter) {
625 if (!errorAlreadyExists(propertyName, errorConstant)) {
626 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(propertyName, errorConstant, parameter);
627 }
628 }
629
630 /**
631 * This method is used to deal with error paths that are not what we expect them to be. This method, along with
632 * resumeErrorPath() are used to temporarily clear the errorPath, and then return it to the original state after the
633 * rule is
634 * executed.
635 *
636 * This method is called at the very beginning of rule enforcement and pulls a copy of the contents of the errorPath
637 * ArrayList
638 * to a local arrayList for temporary storage.
639 */
640 protected void clearErrorPath() {
641
642 // add all the items from the global list to the local list
643 priorErrorPath.addAll(GlobalVariables.getMessageMap().getErrorPath());
644
645 // clear the global list
646 GlobalVariables.getMessageMap().getErrorPath().clear();
647 }
648
649 /**
650 * This method is used to deal with error paths that are not what we expect them to be. This method, along with
651 * clearErrorPath()
652 * are used to temporarily clear the errorPath, and then return it to the original state after the rule is
653 * executed.
654 *
655 * This method is called at the very end of the rule enforcement, and returns the temporarily stored copy of the
656 * errorPath to
657 * the global errorPath, so that no other classes are interrupted.
658 */
659 protected void resumeErrorPath() {
660 // revert the global errorPath back to what it was when we entered this
661 // class
662 GlobalVariables.getMessageMap().getErrorPath().addAll(priorErrorPath);
663 }
664
665 /**
666 * This method executes the DataDictionary Validation against the document.
667 *
668 * @param document
669 * @return true if it passes DD validation, false otherwise
670 */
671 protected boolean dataDictionaryValidate(MaintenanceDocument document) {
672 LOG.debug("MaintenanceDocument validation beginning");
673
674 // explicitly put the errorPath that the dictionaryValidationService
675 // requires
676 GlobalVariables.getMessageMap().addToErrorPath("document.newMaintainableObject");
677
678 // document must have a newMaintainable object
679 Maintainable newMaintainable = document.getNewMaintainableObject();
680 if (newMaintainable == null) {
681 GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject");
682 throw new ValidationException(
683 "Maintainable object from Maintenance Document '" + document.getDocumentTitle() +
684 "' is null, unable to proceed.");
685 }
686
687 // document's newMaintainable must contain an object (ie, not null)
688 Object dataObject = newMaintainable.getDataObject();
689 if (dataObject == null) {
690 GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject.");
691 throw new ValidationException("Maintainable's component business object is null.");
692 }
693
694 // if the Maintainable object is a PBO and there is a legacy maintDefinition
695 // then use the old validation methods
696 if (newBo instanceof PersistableBusinessObject && CollectionUtils.isNotEmpty(maintDocDictionaryService
697 .getMaintainableSections(document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()))) {
698
699 BusinessObject businessObject = (BusinessObject) newBo;
700
701 // run required check from maintenance data dictionary
702 maintDocDictionaryService.validateMaintenanceRequiredFields(document);
703
704 //check for duplicate entries in collections if necessary
705 maintDocDictionaryService.validateMaintainableCollectionsForDuplicateEntries(document);
706
707 // run the DD DictionaryValidation (non-recursive)
708 dictionaryValidationService.validateBusinessObjectOnMaintenanceDocument(businessObject,
709 document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
710
711 // do default (ie, mandatory) existence checks
712 dictionaryValidationService.validateDefaultExistenceChecks(businessObject);
713 } else {
714 GlobalVariables.getMessageMap().addToErrorPath("dataObject");
715
716 dictionaryValidationService.validate(newBo);
717
718 GlobalVariables.getMessageMap().removeFromErrorPath("dataObject");
719 }
720
721 // explicitly remove the errorPath we've added
722 GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject");
723
724 LOG.debug("MaintenanceDocument validation ending");
725 return true;
726 }
727
728 /**
729 * This method checks the two major cases that may violate primary key integrity.
730 *
731 * 1. Disallow changing of the primary keys on an EDIT maintenance document. Other fields can be changed, but once
732 * the primary
733 * keys have been set, they are permanent.
734 *
735 * 2. Disallow creating a new object whose primary key values are already present in the system on a CREATE NEW
736 * maintenance
737 * document.
738 *
739 * This method also will add new Errors to the Global Error Map.
740 *
741 * @param document - The Maintenance Document being tested.
742 * @return Returns false if either test failed, otherwise returns true.
743 */
744 protected boolean primaryKeyCheck(MaintenanceDocument document) {
745
746 // default to success if no failures
747 boolean success = true;
748 Class<?> boClass = document.getNewMaintainableObject().getDataObjectClass();
749
750 Object oldBo = document.getOldMaintainableObject().getDataObject();
751 Object newBo = document.getNewMaintainableObject().getDataObject();
752
753 // We dont do primaryKeyChecks on Global Business Object maintenance documents. This is
754 // because it doesnt really make any sense to do so, given the behavior of Globals. When a
755 // Global Document completes, it will update or create a new record for each BO in the list.
756 // As a result, there's no problem with having existing BO records in the system, they will
757 // simply get updated.
758 if (newBo instanceof GlobalBusinessObject) {
759 return success;
760 }
761
762 // fail and complain if the person has changed the primary keys on
763 // an EDIT maintenance document.
764 if (document.isEdit()) {
765 if (!KRADServiceLocatorWeb.getLegacyDataAdapter().equalsByPrimaryKeys(oldBo, newBo)) {
766 // add a complaint to the errors
767 putDocumentError(KRADConstants.DOCUMENT_ERRORS,
768 RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_PRIMARY_KEYS_CHANGED_ON_EDIT,
769 getHumanReadablePrimaryKeyFieldNames(boClass));
770 success &= false;
771 }
772 }
773
774 // fail and complain if the person has selected a new object with keys that already exist
775 // in the DB.
776 else if (document.isNew()) {
777
778 // TODO: when/if we have standard support for DO retrieval, do this check for DO's
779 if (newBo instanceof PersistableBusinessObject) {
780
781 // get a map of the pk field names and values
782 Map<String, ?> newPkFields = getDataObjectMetaDataService().getPrimaryKeyFieldValues(newBo);
783
784 // TODO: Good suggestion from Aaron, dont bother checking the DB, if all of the
785 // objects PK fields dont have values. If any are null or empty, then
786 // we're done. The current way wont fail, but it will make a wasteful
787 // DB call that may not be necessary, and we want to minimize these.
788
789 // attempt to do a lookup, see if this object already exists by these Primary Keys
790 PersistableBusinessObject testBo =
791 boService.findByPrimaryKey(boClass.asSubclass(PersistableBusinessObject.class), newPkFields);
792
793 // if the retrieve was successful, then this object already exists, and we need
794 // to complain
795 if (testBo != null) {
796 putDocumentError(KRADConstants.DOCUMENT_ERRORS,
797 RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_KEYS_ALREADY_EXIST_ON_CREATE_NEW,
798 getHumanReadablePrimaryKeyFieldNames(boClass));
799 success &= false;
800 }
801 }
802 }
803
804 return success;
805 }
806
807 /**
808 * This method creates a human-readable string of the class' primary key field names, as designated by the
809 * DataDictionary.
810 *
811 * @param boClass
812 * @return
813 */
814 protected String getHumanReadablePrimaryKeyFieldNames(Class<?> boClass) {
815
816 String delim = "";
817 StringBuffer pkFieldNames = new StringBuffer();
818
819 // get a list of all the primary key field names, walk through them
820 List<String> pkFields = KRADServiceLocatorWeb.getLegacyDataAdapter().listPrimaryKeyFieldNames(boClass);
821 for (Iterator<String> iter = pkFields.iterator(); iter.hasNext(); ) {
822 String pkFieldName = (String) iter.next();
823
824 // TODO should this be getting labels from the view dictionary
825 // use the DataDictionary service to translate field name into human-readable label
826 String humanReadableFieldName = ddService.getAttributeLabel(boClass, pkFieldName);
827
828 // append the next field
829 pkFieldNames.append(delim + humanReadableFieldName);
830
831 // separate names with commas after the first one
832 if (delim.equalsIgnoreCase("")) {
833 delim = ", ";
834 }
835 }
836
837 return pkFieldNames.toString();
838 }
839
840 /**
841 * This method enforces all business rules that are common to all maintenance documents which must be tested before
842 * doing an
843 * approval.
844 *
845 * It can be overloaded in special cases where a MaintenanceDocument has very special needs that would be contrary
846 * to what is
847 * enforced here.
848 *
849 * @param document - a populated MaintenanceDocument instance
850 * @return true if the document can be approved, false if not
851 */
852 protected boolean processGlobalApproveDocumentBusinessRules(MaintenanceDocument document) {
853 return true;
854 }
855
856 /**
857 * This method enforces all business rules that are common to all maintenance documents which must be tested before
858 * doing a
859 * route.
860 *
861 * It can be overloaded in special cases where a MaintenanceDocument has very special needs that would be contrary
862 * to what is
863 * enforced here.
864 *
865 * @param document - a populated MaintenanceDocument instance
866 * @return true if the document can be routed, false if not
867 */
868 protected boolean processGlobalRouteDocumentBusinessRules(MaintenanceDocument document) {
869
870 boolean success = true;
871
872 // require a document description field
873 success &= checkEmptyDocumentField(
874 KRADPropertyConstants.DOCUMENT_HEADER + "." + KRADPropertyConstants.DOCUMENT_DESCRIPTION,
875 document.getDocumentHeader().getDocumentDescription(), "Description");
876
877 return success;
878 }
879
880 /**
881 * This method enforces all business rules that are common to all maintenance documents which must be tested before
882 * doing a
883 * save.
884 *
885 * It can be overloaded in special cases where a MaintenanceDocument has very special needs that would be contrary
886 * to what is
887 * enforced here.
888 *
889 * Note that although this method returns a true or false to indicate whether the save should happen or not, this
890 * result may not
891 * be followed by the calling method. In other words, the boolean result will likely be ignored, and the document
892 * saved,
893 * regardless.
894 *
895 * @param document - a populated MaintenanceDocument instance
896 * @return true if all business rules succeed, false if not
897 */
898 protected boolean processGlobalSaveDocumentBusinessRules(MaintenanceDocument document) {
899
900 // default to success
901 boolean success = true;
902
903 // do generic checks that impact primary key violations
904 primaryKeyCheck(document);
905
906 // this is happening only on the processSave, since a Save happens in both the
907 // Route and Save events.
908 this.dataDictionaryValidate(document);
909
910 return success;
911 }
912
913 /**
914 * This method should be overridden to provide custom rules for processing document saving
915 *
916 * @param document
917 * @return boolean
918 */
919 protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
920 return true;
921 }
922
923 /**
924 * This method should be overridden to provide custom rules for processing document routing
925 *
926 * @param document
927 * @return boolean
928 */
929 protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
930 return true;
931 }
932
933 /**
934 * This method should be overridden to provide custom rules for processing document approval.
935 *
936 * @param document
937 * @return booelan
938 */
939 protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
940 return true;
941 }
942
943 // Document Validation Helper Methods
944
945 /**
946 * This method checks to see if the document is in a state that it can be saved without causing exceptions.
947 *
948 * Note that Business Rules are NOT enforced here, only validity checks.
949 *
950 * This method will only return false if the document is in such a state that routing it will cause
951 * RunTimeExceptions.
952 *
953 * @param maintenanceDocument - a populated MaintenaceDocument instance.
954 * @return boolean - returns true unless the object is in an invalid state.
955 */
956 protected boolean isDocumentValidForSave(MaintenanceDocument maintenanceDocument) {
957
958 boolean success = true;
959
960 success &= super.isDocumentOverviewValid(maintenanceDocument);
961 success &= validateDocumentStructure((Document) maintenanceDocument);
962 success &= validateMaintenanceDocument(maintenanceDocument);
963 success &= validateGlobalBusinessObjectPersistable(maintenanceDocument);
964 return success;
965 }
966
967 /**
968 * This method makes sure the document itself is valid, and has the necessary fields populated to be routable.
969 *
970 * This is not a business rules test, rather its a structure test to make sure that the document will not cause
971 * exceptions
972 * before routing.
973 *
974 * @param document - document to be tested
975 * @return false if the document is missing key values, true otherwise
976 */
977 protected boolean validateDocumentStructure(Document document) {
978 boolean success = true;
979
980 // document must have a populated documentNumber
981 String documentHeaderId = document.getDocumentNumber();
982 if (documentHeaderId == null || StringUtils.isEmpty(documentHeaderId)) {
983 throw new ValidationException("Document has no document number, unable to proceed.");
984 }
985
986 return success;
987 }
988
989 /**
990 * This method checks to make sure the document is a valid maintenanceDocument, and has the necessary values
991 * populated such that
992 * it will not cause exceptions in later routing or business rules testing.
993 *
994 * This is not a business rules test.
995 *
996 * @param maintenanceDocument - document to be tested
997 * @return whether maintenance doc passes
998 * @throws org.kuali.rice.krad.exception.ValidationException
999 *
1000 */
1001 protected boolean validateMaintenanceDocument(MaintenanceDocument maintenanceDocument) {
1002 boolean success = true;
1003 Maintainable newMaintainable = maintenanceDocument.getNewMaintainableObject();
1004
1005 // document must have a newMaintainable object
1006 if (newMaintainable == null) {
1007 throw new ValidationException(
1008 "Maintainable object from Maintenance Document '" + maintenanceDocument.getDocumentTitle() +
1009 "' is null, unable to proceed.");
1010 }
1011
1012 // document's newMaintainable must contain an object (ie, not null)
1013 if (newMaintainable.getDataObject() == null) {
1014 throw new ValidationException("Maintainable's component data object is null.");
1015 }
1016
1017 return success;
1018 }
1019
1020 /**
1021 * This method checks whether this maint doc contains Global Business Objects, and if so, whether the GBOs are in a
1022 * persistable
1023 * state. This will return false if this method determines that the GBO will cause a SQL Exception when the document
1024 * is
1025 * persisted.
1026 *
1027 * @param document
1028 * @return False when the method determines that the contained Global Business Object will cause a SQL Exception,
1029 * and the
1030 * document should not be saved. It will return True otherwise.
1031 */
1032 protected boolean validateGlobalBusinessObjectPersistable(MaintenanceDocument document) {
1033 boolean success = true;
1034
1035 if (document.getNewMaintainableObject() == null) {
1036 return success;
1037 }
1038 if (document.getNewMaintainableObject().getDataObject() == null) {
1039 return success;
1040 }
1041 if (!(document.getNewMaintainableObject().getDataObject() instanceof GlobalBusinessObject)) {
1042 return success;
1043 }
1044
1045 PersistableBusinessObject bo = (PersistableBusinessObject) document.getNewMaintainableObject().getDataObject();
1046 GlobalBusinessObject gbo = (GlobalBusinessObject) bo;
1047 return gbo.isPersistable();
1048 }
1049
1050 /**
1051 * This method tests to make sure the MaintenanceDocument passed in is based on the class you are expecting.
1052 *
1053 * It does this based on the NewMaintainableObject of the MaintenanceDocument.
1054 *
1055 * @param document - MaintenanceDocument instance you want to test
1056 * @param clazz - class you are expecting the MaintenanceDocument to be based on
1057 * @return true if they match, false if not
1058 */
1059 protected boolean isCorrectMaintenanceClass(MaintenanceDocument document, Class clazz) {
1060
1061 // disallow null arguments
1062 if (document == null || clazz == null) {
1063 throw new IllegalArgumentException("Null arguments were passed in.");
1064 }
1065
1066 // compare the class names
1067 if (clazz.toString().equals(document.getNewMaintainableObject().getDataObjectClass().toString())) {
1068 return true;
1069 } else {
1070 return false;
1071 }
1072 }
1073
1074 /**
1075 * This method accepts an object, and attempts to determine whether it is empty by this method's definition.
1076 *
1077 * OBJECT RESULT null false empty-string false whitespace false otherwise true
1078 *
1079 * If the result is false, it will add an object field error to the Global Errors.
1080 *
1081 * @param valueToTest - any object to test, usually a String
1082 * @param propertyName - the name of the property being tested
1083 * @return true or false, by the description above
1084 */
1085 protected boolean checkEmptyBOField(String propertyName, Object valueToTest, String parameter) {
1086
1087 boolean success = true;
1088
1089 success = checkEmptyValue(valueToTest);
1090
1091 // if failed, then add a field error
1092 if (!success) {
1093 putFieldError(propertyName, RiceKeyConstants.ERROR_REQUIRED, parameter);
1094 }
1095
1096 return success;
1097 }
1098
1099 /**
1100 * This method accepts document field (such as , and attempts to determine whether it is empty by this method's
1101 * definition.
1102 *
1103 * OBJECT RESULT null false empty-string false whitespace false otherwise true
1104 *
1105 * If the result is false, it will add document field error to the Global Errors.
1106 *
1107 * @param valueToTest - any object to test, usually a String
1108 * @param propertyName - the name of the property being tested
1109 * @return true or false, by the description above
1110 */
1111 protected boolean checkEmptyDocumentField(String propertyName, Object valueToTest, String parameter) {
1112 boolean success = true;
1113 success = checkEmptyValue(valueToTest);
1114 if (!success) {
1115 putDocumentError(propertyName, RiceKeyConstants.ERROR_REQUIRED, parameter);
1116 }
1117 return success;
1118 }
1119
1120 /**
1121 * This method accepts document field (such as , and attempts to determine whether it is empty by this method's
1122 * definition.
1123 *
1124 * OBJECT RESULT null false empty-string false whitespace false otherwise true
1125 *
1126 * It will the result as a boolean
1127 *
1128 * @param valueToTest - any object to test, usually a String
1129 */
1130 protected boolean checkEmptyValue(Object valueToTest) {
1131 boolean success = true;
1132
1133 // if its not a string, only fail if its a null object
1134 if (valueToTest == null) {
1135 success = false;
1136 } else {
1137 // test for null, empty-string, or whitespace if its a string
1138 if (valueToTest instanceof String) {
1139 if (StringUtils.isBlank((String) valueToTest)) {
1140 success = false;
1141 }
1142 }
1143 }
1144
1145 return success;
1146 }
1147
1148 /**
1149 * This method is used during debugging to dump the contents of the error map, including the key names. It is not
1150 * used by the
1151 * application in normal circumstances at all.
1152 */
1153 protected void showErrorMap() {
1154
1155 if (GlobalVariables.getMessageMap().hasNoErrors()) {
1156 return;
1157 }
1158
1159 for (Iterator i = GlobalVariables.getMessageMap().getAllPropertiesAndErrors().iterator(); i.hasNext(); ) {
1160 Map.Entry e = (Map.Entry) i.next();
1161
1162 AutoPopulatingList errorList = (AutoPopulatingList) e.getValue();
1163 for (Iterator j = errorList.iterator(); j.hasNext(); ) {
1164 ErrorMessage em = (ErrorMessage) j.next();
1165
1166 if (em.getMessageParameters() == null) {
1167 LOG.error(e.getKey().toString() + " = " + em.getErrorKey());
1168 } else {
1169 LOG.error(e.getKey().toString() + " = " + em.getErrorKey() + " : " +
1170 em.getMessageParameters().toString());
1171 }
1172 }
1173 }
1174 }
1175
1176 /**
1177 * @see org.kuali.rice.krad.rules.MaintenanceDocumentRule#setupBaseConvenienceObjects(org.kuali.rice.krad.maintenance.MaintenanceDocument)
1178 */
1179 public void setupBaseConvenienceObjects(MaintenanceDocument document) {
1180
1181 // setup oldAccount convenience objects, make sure all possible sub-objects are populated
1182 oldBo = document.getOldMaintainableObject().getDataObject();
1183 if (oldBo != null && oldBo instanceof PersistableBusinessObject) {
1184 ((PersistableBusinessObject) oldBo).refreshNonUpdateableReferences();
1185 }
1186
1187 // setup newAccount convenience objects, make sure all possible sub-objects are populated
1188 newBo = document.getNewMaintainableObject().getDataObject();
1189 if (newBo instanceof PersistableBusinessObject) {
1190 ((PersistableBusinessObject) newBo).refreshNonUpdateableReferences();
1191 }
1192
1193 boClass = document.getNewMaintainableObject().getDataObjectClass();
1194
1195 // call the setupConvenienceObjects in the subclass, if a subclass exists
1196 setupConvenienceObjects();
1197 }
1198
1199 public void setupConvenienceObjects() {
1200 // should always be overriden by subclass
1201 }
1202
1203 /**
1204 * This method checks to make sure that if the foreign-key fields for the given reference attributes have any fields
1205 * filled out,
1206 * that all fields are filled out.
1207 *
1208 * If any are filled out, but all are not, it will return false and add a global error message about the problem.
1209 *
1210 * @param referenceName - The name of the reference object, whose foreign-key fields must be all-or-none filled
1211 * out.
1212 * @return true if this is the case, false if not
1213 */
1214 protected boolean checkForPartiallyFilledOutReferenceForeignKeys(String referenceName) {
1215 boolean success = true;
1216
1217 if (newBo instanceof PersistableBusinessObject) {
1218 ForeignKeyFieldsPopulationState fkFieldsState;
1219 fkFieldsState = persistenceStructureService
1220 .getForeignKeyFieldsPopulationState((PersistableBusinessObject) newBo, referenceName);
1221
1222 // determine result
1223 if (fkFieldsState.isAnyFieldsPopulated() && !fkFieldsState.isAllFieldsPopulated()) {
1224 success = false;
1225
1226 // add errors if appropriate
1227
1228 // get the full set of foreign-keys
1229 List fKeys = new ArrayList(persistenceStructureService
1230 .getForeignKeysForReference(newBo.getClass().asSubclass(PersistableBusinessObject.class),
1231 referenceName).keySet());
1232 String fKeysReadable = consolidateFieldNames(fKeys, ", ").toString();
1233
1234 // walk through the missing fields
1235 for (Iterator iter = fkFieldsState.getUnpopulatedFieldNames().iterator(); iter.hasNext(); ) {
1236 String fieldName = (String) iter.next();
1237
1238 // get the human-readable name
1239 String fieldNameReadable = ddService.getAttributeLabel(newBo.getClass(), fieldName);
1240
1241 // add a field error
1242 putFieldError(fieldName, RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_PARTIALLY_FILLED_OUT_REF_FKEYS,
1243 new String[]{fieldNameReadable, fKeysReadable});
1244 }
1245 }
1246 }
1247
1248 return success;
1249 }
1250
1251 /**
1252 * This method turns a list of field property names, into a delimited string of the human-readable names.
1253 *
1254 * @param fieldNames - List of fieldNames
1255 * @return A filled StringBuffer ready to go in an error message
1256 */
1257 protected StringBuffer consolidateFieldNames(List fieldNames, String delimiter) {
1258
1259 StringBuffer sb = new StringBuffer();
1260
1261 // setup some vars
1262 boolean firstPass = true;
1263 String delim = "";
1264
1265 // walk through the list
1266 for (Iterator iter = fieldNames.iterator(); iter.hasNext(); ) {
1267 String fieldName = (String) iter.next();
1268
1269 // get the human-readable name
1270 // add the new one, with the appropriate delimiter
1271 sb.append(delim + ddService.getAttributeLabel(newBo.getClass(), fieldName));
1272
1273 // after the first item, start using a delimiter
1274 if (firstPass) {
1275 delim = delimiter;
1276 firstPass = false;
1277 }
1278 }
1279
1280 return sb;
1281 }
1282
1283 /**
1284 * This method translates the passed in field name into a human-readable attribute label.
1285 *
1286 * It assumes the existing newBO's class as the class to examine the fieldName for.
1287 *
1288 * @param fieldName The fieldName you want a human-readable label for.
1289 * @return A human-readable label, pulled from the DataDictionary.
1290 */
1291 protected String getFieldLabel(String fieldName) {
1292 return ddService.getAttributeLabel(newBo.getClass(), fieldName) + "(" +
1293 ddService.getAttributeShortLabel(newBo.getClass(), fieldName) + ")";
1294 }
1295
1296 /**
1297 * This method translates the passed in field name into a human-readable attribute label.
1298 *
1299 * It assumes the existing newBO's class as the class to examine the fieldName for.
1300 *
1301 * @param boClass The class to use in combination with the fieldName.
1302 * @param fieldName The fieldName you want a human-readable label for.
1303 * @return A human-readable label, pulled from the DataDictionary.
1304 */
1305 protected String getFieldLabel(Class boClass, String fieldName) {
1306 return ddService.getAttributeLabel(boClass, fieldName) + "(" +
1307 ddService.getAttributeShortLabel(boClass, fieldName) + ")";
1308 }
1309
1310 /**
1311 * Gets the boService attribute.
1312 *
1313 * @return Returns the boService.
1314 */
1315 protected final BusinessObjectService getBoService() {
1316 return boService;
1317 }
1318
1319 /**
1320 * Sets the boService attribute value.
1321 *
1322 * @param boService The boService to set.
1323 */
1324 public final void setBoService(BusinessObjectService boService) {
1325 this.boService = boService;
1326 }
1327
1328 /**
1329 * Gets the configService attribute.
1330 *
1331 * @return Returns the configService.
1332 */
1333 protected final ConfigurationService getConfigService() {
1334 return configService;
1335 }
1336
1337 /**
1338 * Sets the configService attribute value.
1339 *
1340 * @param configService The configService to set.
1341 */
1342 public final void setConfigService(ConfigurationService configService) {
1343 this.configService = configService;
1344 }
1345
1346 /**
1347 * Gets the ddService attribute.
1348 *
1349 * @return Returns the ddService.
1350 */
1351 protected final DataDictionaryService getDdService() {
1352 return ddService;
1353 }
1354
1355 /**
1356 * Sets the ddService attribute value.
1357 *
1358 * @param ddService The ddService to set.
1359 */
1360 public final void setDdService(DataDictionaryService ddService) {
1361 this.ddService = ddService;
1362 }
1363
1364 /**
1365 * Gets the dictionaryValidationService attribute.
1366 *
1367 * @return Returns the dictionaryValidationService.
1368 */
1369 protected final DictionaryValidationService getDictionaryValidationService() {
1370 return dictionaryValidationService;
1371 }
1372
1373 /**
1374 * Sets the dictionaryValidationService attribute value.
1375 *
1376 * @param dictionaryValidationService The dictionaryValidationService to set.
1377 */
1378 public final void setDictionaryValidationService(DictionaryValidationService dictionaryValidationService) {
1379 this.dictionaryValidationService = dictionaryValidationService;
1380 }
1381
1382 /**
1383 * Gets the maintDocDictionaryService attribute.
1384 *
1385 * @return Returns the maintDocDictionaryService.
1386 */
1387 protected final MaintenanceDocumentDictionaryService getMaintDocDictionaryService() {
1388 return maintDocDictionaryService;
1389 }
1390
1391 /**
1392 * Sets the maintDocDictionaryService attribute value.
1393 *
1394 * @param maintDocDictionaryService The maintDocDictionaryService to set.
1395 */
1396 public final void setMaintDocDictionaryService(MaintenanceDocumentDictionaryService maintDocDictionaryService) {
1397 this.maintDocDictionaryService = maintDocDictionaryService;
1398 }
1399
1400 /**
1401 * Gets the newBo attribute.
1402 *
1403 * @return Returns the newBo.
1404 */
1405 protected final Object getNewBo() {
1406 return newBo;
1407 }
1408
1409 protected void setNewBo(Object newBo) {
1410 this.newBo = newBo;
1411 }
1412
1413 /**
1414 * Gets the oldBo attribute.
1415 *
1416 * @return Returns the oldBo.
1417 */
1418 protected final Object getOldBo() {
1419 return oldBo;
1420 }
1421
1422 /**
1423 * Gets the persistenceStructureService attribute.
1424 *
1425 * @return Returns the persistenceStructureService.
1426 */
1427 protected final PersistenceStructureService getPersistenceStructureService() {
1428 return persistenceStructureService;
1429 }
1430
1431 /**
1432 * Sets the persistenceStructureService attribute value.
1433 *
1434 * @param persistenceStructureService The persistenceStructureService to set.
1435 */
1436 public final void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
1437 this.persistenceStructureService = persistenceStructureService;
1438 }
1439
1440 /**
1441 * Gets the workflowDocumentService attribute.
1442 *
1443 * @return Returns the workflowDocumentService.
1444 */
1445 public WorkflowDocumentService getWorkflowDocumentService() {
1446 return workflowDocumentService;
1447 }
1448
1449 /**
1450 * Sets the workflowDocumentService attribute value.
1451 *
1452 * @param workflowDocumentService The workflowDocumentService to set.
1453 */
1454 public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
1455 this.workflowDocumentService = workflowDocumentService;
1456 }
1457
1458 public boolean processAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName,
1459 PersistableBusinessObject bo) {
1460 LOG.debug("processAddCollectionLineBusinessRules");
1461
1462 // setup convenience pointers to the old & new bo
1463 setupBaseConvenienceObjects(document);
1464
1465 // sanity check on the document object
1466 this.validateMaintenanceDocument(document);
1467
1468 boolean success = true;
1469 MessageMap map = GlobalVariables.getMessageMap();
1470 int errorCount = map.getErrorCount();
1471 map.addToErrorPath(MAINTAINABLE_ERROR_PATH);
1472 if (LOG.isDebugEnabled()) {
1473 LOG.debug("processAddCollectionLineBusinessRules - BO: " + bo);
1474 LOG.debug("Before Validate: " + map);
1475 }
1476 //getBoDictionaryService().performForceUppercase(bo);
1477 getMaintDocDictionaryService().validateMaintainableCollectionsAddLineRequiredFields(document,
1478 document.getNewMaintainableObject().getBusinessObject(), collectionName);
1479 String errorPath = KRADConstants.MAINTENANCE_ADD_PREFIX + collectionName;
1480 map.addToErrorPath(errorPath);
1481
1482 getDictionaryValidationService().validateBusinessObject(bo, false);
1483 success &= map.getErrorCount() == errorCount;
1484 success &= dictionaryValidationService.validateDefaultExistenceChecksForNewCollectionItem(
1485 document.getNewMaintainableObject().getBusinessObject(), bo, collectionName);
1486 success &= validateDuplicateIdentifierInDataDictionary(document, collectionName, bo);
1487 success &= processCustomAddCollectionLineBusinessRules(document, collectionName, bo);
1488
1489 map.removeFromErrorPath(errorPath);
1490 map.removeFromErrorPath(MAINTAINABLE_ERROR_PATH);
1491 if (LOG.isDebugEnabled()) {
1492 LOG.debug("After Validate: " + map);
1493 LOG.debug("processAddCollectionLineBusinessRules returning: " + success);
1494 }
1495
1496 return success;
1497 }
1498
1499 /**
1500 * This method validates that there should only exist one entry in the collection whose
1501 * fields match the fields specified within the duplicateIdentificationFields in the
1502 * maintenance document data dictionary.
1503 * If the duplicateIdentificationFields is not specified in the DD, by default it would
1504 * allow the addition to happen and return true.
1505 * It will return false if it fails the uniqueness validation.
1506 *
1507 * @param document
1508 * @param collectionName
1509 * @param bo
1510 * @return
1511 */
1512 protected boolean validateDuplicateIdentifierInDataDictionary(MaintenanceDocument document, String collectionName,
1513 PersistableBusinessObject bo) {
1514 boolean valid = true;
1515 PersistableBusinessObject maintBo = document.getNewMaintainableObject().getBusinessObject();
1516 Collection maintCollection = (Collection) ObjectUtils.getPropertyValue(maintBo, collectionName);
1517 List<String> duplicateIdentifier = document.getNewMaintainableObject()
1518 .getDuplicateIdentifierFieldsFromDataDictionary(
1519 document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName(), collectionName);
1520 if (duplicateIdentifier.size() > 0) {
1521 List<String> existingIdentifierString = document.getNewMaintainableObject()
1522 .getMultiValueIdentifierList(maintCollection, duplicateIdentifier);
1523 if (document.getNewMaintainableObject()
1524 .hasBusinessObjectExisted(bo, existingIdentifierString, duplicateIdentifier)) {
1525 valid = false;
1526 GlobalVariables.getMessageMap()
1527 .putError(duplicateIdentifier.get(0), RiceKeyConstants.ERROR_DUPLICATE_ELEMENT, "entries in ",
1528 document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
1529 }
1530 }
1531 return valid;
1532 }
1533
1534 public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName,
1535 PersistableBusinessObject line) {
1536 return true;
1537 }
1538
1539 public PersonService getPersonService() {
1540 return personService;
1541 }
1542
1543 public void setPersonService(PersonService personService) {
1544 this.personService = personService;
1545 }
1546
1547 public DateTimeService getDateTimeService() {
1548 return CoreApiServiceLocator.getDateTimeService();
1549 }
1550
1551 /**
1552 * @return the documentHelperService
1553 */
1554 public DocumentHelperService getDocumentHelperService() {
1555 return this.documentHelperService;
1556 }
1557
1558 /**
1559 * @param documentHelperService the documentHelperService to set
1560 */
1561 public void setDocumentHelperService(DocumentHelperService documentHelperService) {
1562 this.documentHelperService = documentHelperService;
1563 }
1564
1565 protected RoleService getRoleService() {
1566 if (this.roleService == null) {
1567 this.roleService = KimApiServiceLocator.getRoleService();
1568 }
1569 return this.roleService;
1570 }
1571
1572 protected DataObjectMetaDataService getDataObjectMetaDataService() {
1573 if (dataObjectMetaDataService == null) {
1574 this.dataObjectMetaDataService = KNSServiceLocator.getDataObjectMetaDataService();
1575 }
1576 return dataObjectMetaDataService;
1577 }
1578
1579 public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
1580 this.dataObjectMetaDataService = dataObjectMetaDataService;
1581 }
1582
1583 public void setBusinessObjectAuthorizationService(
1584 BusinessObjectAuthorizationService businessObjectAuthorizationService) {
1585 this.businessObjectAuthorizationService = businessObjectAuthorizationService;
1586 }
1587
1588 public void setBusinessObjectMetaDataService(BusinessObjectMetaDataService businessObjectMetaDataService) {
1589 this.businessObjectMetaDataService = businessObjectMetaDataService;
1590 }
1591
1592 public void setBoDictionaryService(BusinessObjectDictionaryService boDictionaryService) {
1593 this.boDictionaryService = boDictionaryService;
1594 }
1595 }
1596