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