001 /**
002 * Copyright 2005-2011 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.krad.maintenance;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
020 import org.kuali.rice.core.api.CoreApiServiceLocator;
021 import org.kuali.rice.core.api.encryption.EncryptionService;
022 import org.kuali.rice.kim.api.identity.Person;
023 import org.kuali.rice.krad.bo.BusinessObject;
024 import org.kuali.rice.krad.bo.DocumentHeader;
025 import org.kuali.rice.krad.bo.Note;
026 import org.kuali.rice.krad.bo.PersistableBusinessObject;
027 import org.kuali.rice.krad.document.MaintenanceDocument;
028 import org.kuali.rice.krad.document.MaintenanceLock;
029 import org.kuali.rice.krad.exception.PessimisticLockingException;
030 import org.kuali.rice.krad.service.BusinessObjectService;
031 import org.kuali.rice.krad.service.DataObjectAuthorizationService;
032 import org.kuali.rice.krad.service.DataObjectMetaDataService;
033 import org.kuali.rice.krad.service.DocumentDictionaryService;
034 import org.kuali.rice.krad.service.KRADServiceLocator;
035 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
036 import org.kuali.rice.krad.service.LookupService;
037 import org.kuali.rice.krad.service.MaintenanceDocumentService;
038 import org.kuali.rice.krad.uif.container.CollectionGroup;
039 import org.kuali.rice.krad.uif.view.View;
040 import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
041 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
042 import org.kuali.rice.krad.util.KRADConstants;
043 import org.kuali.rice.krad.util.ObjectUtils;
044 import org.kuali.rice.krad.web.form.MaintenanceForm;
045
046 import java.security.GeneralSecurityException;
047 import java.util.ArrayList;
048 import java.util.Collection;
049 import java.util.Iterator;
050 import java.util.List;
051 import java.util.Map;
052
053 /**
054 * Default implementation of the <code>Maintainable</code> interface
055 *
056 * @author Kuali Rice Team (rice.collab@kuali.org)
057 */
058 public class MaintainableImpl extends ViewHelperServiceImpl implements Maintainable {
059 private static final long serialVersionUID = 9125271369161634992L;
060
061 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintainableImpl.class);
062
063 private String documentNumber;
064 private Object dataObject;
065 private Class<?> dataObjectClass;
066 private String maintenanceAction;
067
068 private transient LookupService lookupService;
069 private transient DataObjectAuthorizationService dataObjectAuthorizationService;
070 private transient DataObjectMetaDataService dataObjectMetaDataService;
071 private transient DocumentDictionaryService documentDictionaryService;
072 private transient EncryptionService encryptionService;
073 private transient BusinessObjectService businessObjectService;
074 private transient MaintenanceDocumentService maintenanceDocumentService;
075
076 /**
077 * @see org.kuali.rice.krad.maintenance.Maintainable#retrieveObjectForEditOrCopy(MaintenanceDocument, java.util.Map)
078 */
079 @Override
080 public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) {
081 Object dataObject = null;
082
083 try {
084 dataObject = getLookupService().findObjectBySearch(getDataObjectClass(), dataObjectKeys);
085 } catch (ClassNotPersistenceCapableException ex) {
086 if (!document.getOldMaintainableObject().isExternalBusinessObject()) {
087 throw new RuntimeException("Data Object Class: "
088 + getDataObjectClass()
089 + " is not persistable and is not externalizable - configuration error");
090 }
091 // otherwise, let fall through
092 }
093
094 return dataObject;
095 }
096
097 /**
098 * @see org.kuali.rice.krad.maintenance.Maintainable#setDocumentNumber
099 */
100 @Override
101 public void setDocumentNumber(String documentNumber) {
102 this.documentNumber = documentNumber;
103 }
104
105 /**
106 * @see org.kuali.rice.krad.maintenance.Maintainable#getDocumentTitle
107 */
108 @Override
109 public String getDocumentTitle(MaintenanceDocument document) {
110 // default implementation is to allow MaintenanceDocumentBase to
111 // generate the doc title
112 return "";
113 }
114
115 /**
116 * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObject
117 */
118 @Override
119 public Object getDataObject() {
120 return dataObject;
121 }
122
123 /**
124 * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObject
125 */
126 @Override
127 public void setDataObject(Object object) {
128 this.dataObject = object;
129 }
130
131 /**
132 * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObjectClass
133 */
134 @Override
135 public Class getDataObjectClass() {
136 return dataObjectClass;
137 }
138
139 /**
140 * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObjectClass
141 */
142 @Override
143 public void setDataObjectClass(Class dataObjectClass) {
144 this.dataObjectClass = dataObjectClass;
145 }
146
147 /**
148 * @see org.kuali.rice.krad.maintenance.Maintainable#getMaintenanceAction
149 */
150 @Override
151 public String getMaintenanceAction() {
152 return maintenanceAction;
153 }
154
155 /**
156 * @see org.kuali.rice.krad.maintenance.Maintainable#setMaintenanceAction
157 */
158 @Override
159 public void setMaintenanceAction(String maintenanceAction) {
160 this.maintenanceAction = maintenanceAction;
161 }
162
163 /**
164 * Note: as currently implemented, every key field for a given
165 * data object class must have a visible getter
166 *
167 * @see org.kuali.rice.krad.maintenance.Maintainable#generateMaintenanceLocks
168 */
169 @Override
170 public List<MaintenanceLock> generateMaintenanceLocks() {
171 List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
172 StringBuffer lockRepresentation = new StringBuffer(dataObjectClass.getName());
173 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_CLASS_DELIM);
174
175 Object bo = getDataObject();
176 List keyFieldNames = getDocumentDictionaryService().getLockingKeys(getDocumentTypeName());
177
178 for (Iterator i = keyFieldNames.iterator(); i.hasNext(); ) {
179 String fieldName = (String) i.next();
180 Object fieldValue = ObjectUtils.getPropertyValue(bo, fieldName);
181 if (fieldValue == null) {
182 fieldValue = "";
183 }
184
185 // check if field is a secure
186 if (getDataObjectAuthorizationService()
187 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass, fieldName)) {
188 try {
189 fieldValue = getEncryptionService().encrypt(fieldValue);
190 } catch (GeneralSecurityException e) {
191 LOG.error("Unable to encrypt secure field for locking representation " + e.getMessage());
192 throw new RuntimeException(
193 "Unable to encrypt secure field for locking representation " + e.getMessage());
194 }
195 }
196
197 lockRepresentation.append(fieldName);
198 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_FIELDNAME_DELIM);
199 lockRepresentation.append(String.valueOf(fieldValue));
200 if (i.hasNext()) {
201 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_VALUE_DELIM);
202 }
203 }
204
205 MaintenanceLock maintenanceLock = new MaintenanceLock();
206 maintenanceLock.setDocumentNumber(documentNumber);
207 maintenanceLock.setLockingRepresentation(lockRepresentation.toString());
208 maintenanceLocks.add(maintenanceLock);
209
210 return maintenanceLocks;
211 }
212
213 /**
214 * Retrieves the document type name from the data dictionary based on
215 * business object class
216 */
217 protected String getDocumentTypeName() {
218 return getDocumentDictionaryService().getMaintenanceDocumentTypeName(dataObjectClass);
219 }
220
221 /**
222 * @see org.kuali.rice.krad.maintenance.Maintainable#saveDataObject
223 */
224 @Override
225 public void saveDataObject() {
226 if (dataObject instanceof PersistableBusinessObject) {
227 getBusinessObjectService().linkAndSave((PersistableBusinessObject) dataObject);
228 } else {
229 throw new RuntimeException(
230 "Cannot save object of type: " + dataObjectClass + " with business object service");
231 }
232 }
233
234 /**
235 * @see org.kuali.rice.krad.maintenance.Maintainable#deleteDataObject
236 */
237 @Override
238 public void deleteDataObject() {
239 if (dataObject == null) {
240 return;
241 }
242
243 if (dataObject instanceof PersistableBusinessObject) {
244 getBusinessObjectService().delete((PersistableBusinessObject) dataObject);
245 dataObject = null;
246 } else {
247 throw new RuntimeException(
248 "Cannot delete object of type: " + dataObjectClass + " with business object service");
249 }
250 }
251
252 /**
253 * @see org.kuali.rice.krad.maintenance.Maintainable#doRouteStatusChange
254 */
255 @Override
256 public void doRouteStatusChange(DocumentHeader documentHeader) {
257 // no default implementation
258 }
259
260 /**
261 * @see org.kuali.rice.krad.maintenance.Maintainable#getLockingDocumentId
262 */
263 @Override
264 public String getLockingDocumentId() {
265 return getMaintenanceDocumentService().getLockingDocumentId(this, documentNumber);
266 }
267
268 /**
269 * @see org.kuali.rice.krad.maintenance.Maintainable#getWorkflowEngineDocumentIdsToLock
270 */
271 @Override
272 public List<String> getWorkflowEngineDocumentIdsToLock() {
273 return null;
274 }
275
276 /**
277 * Default implementation simply returns false to indicate that custom
278 * lock descriptors are not supported by MaintainableImpl. If custom
279 * lock descriptors are needed, the appropriate subclasses should override
280 * this method
281 *
282 * @see org.kuali.rice.krad.maintenance.Maintainable#useCustomLockDescriptors
283 */
284 @Override
285 public boolean useCustomLockDescriptors() {
286 return false;
287 }
288
289 /**
290 * Default implementation just throws a PessimisticLockingException.
291 * Subclasses of MaintainableImpl that need support for custom lock
292 * descriptors should override this method
293 *
294 * @see org.kuali.rice.krad.maintenance.Maintainable#getCustomLockDescriptor
295 */
296 @Override
297 public String getCustomLockDescriptor(Person user) {
298 throw new PessimisticLockingException("The Maintainable for document " + documentNumber +
299 " is using pessimistic locking with custom lock descriptors, but the Maintainable has not overridden the getCustomLockDescriptor method");
300 }
301
302 /**
303 * @see org.kuali.rice.krad.maintenance.Maintainable#isNotesEnabled
304 */
305 @Override
306 public boolean isNotesEnabled() {
307 return getDataObjectMetaDataService().areNotesSupported(dataObjectClass);
308 }
309
310 /**
311 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isExternalBusinessObject
312 */
313 @Override
314 public boolean isExternalBusinessObject() {
315 return false;
316 }
317
318 /**
319 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#prepareExternalBusinessObject
320 */
321 @Override
322 public void prepareExternalBusinessObject(BusinessObject businessObject) {
323 // by default do nothing
324 }
325
326 /**
327 * Checks whether the data object is not null and has its primary key values populated
328 *
329 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isOldDataObjectInDocument
330 */
331 @Override
332 public boolean isOldDataObjectInDocument() {
333 boolean isOldDataObjectInExistence = true;
334
335 if (getDataObject() == null) {
336 isOldDataObjectInExistence = false;
337 } else {
338 Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues(getDataObject());
339 for (Object keyValue : keyFieldValues.values()) {
340 if (keyValue == null) {
341 isOldDataObjectInExistence = false;
342 } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) {
343 isOldDataObjectInExistence = false;
344 }
345
346 if (!isOldDataObjectInExistence) {
347 break;
348 }
349 }
350 }
351
352 return isOldDataObjectInExistence;
353 }
354
355 /**
356 * @see org.kuali.rice.krad.maintenance.Maintainable#prepareForSave
357 */
358 @Override
359 public void prepareForSave() {
360 // by default do nothing
361 }
362
363 /**
364 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterRetrieve
365 */
366 @Override
367 public void processAfterRetrieve() {
368 // by default do nothing
369 }
370
371 /**
372 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#setupNewFromExisting
373 */
374 @Override
375 public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) {
376 // by default do nothing
377 }
378
379 /**
380 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterCopy
381 */
382 @Override
383 public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> requestParameters) {
384 // by default do nothing
385 }
386
387 /**
388 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterEdit
389 */
390 @Override
391 public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
392 // by default do nothing
393 }
394
395 /**
396 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterNew
397 */
398 @Override
399 public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
400 // by default do nothing
401 }
402
403 /**
404 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterPost
405 */
406 @Override
407 public void processAfterPost(MaintenanceDocument document, Map<String, String[]> requestParameters) {
408 // by default do nothing
409 }
410
411 /**
412 * In the case of edit maintenance adds a new blank line to the old side
413 *
414 * TODO: should this write some sort of missing message on the old side
415 * instead?
416 *
417 * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterAddLine(org.kuali.rice.krad.uif.view.View,
418 * org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,
419 * java.lang.Object)
420 */
421 @Override
422 protected void processAfterAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
423 super.processAfterAddLine(view, collectionGroup, model, addLine);
424
425 // Check for maintenance documents in edit but exclude notes
426 if (model instanceof MaintenanceForm && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceForm)model).getMaintenanceAction()) && !(addLine instanceof Note)) {
427 MaintenanceForm maintenanceForm = (MaintenanceForm) model;
428 MaintenanceDocument document = maintenanceForm.getDocument();
429
430 // get the old object's collection
431 Collection<Object> oldCollection = ObjectPropertyUtils
432 .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
433 collectionGroup.getPropertyName());
434 try {
435 Object blankLine = collectionGroup.getCollectionObjectClass().newInstance();
436 oldCollection.add(blankLine);
437 } catch (Exception e) {
438 throw new RuntimeException("Unable to create new line instance for old maintenance object", e);
439 }
440 }
441 }
442
443 /**
444 * In the case of edit maintenance deleted the item on the old side
445 *
446 *
447 * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterDeleteLine(View,
448 * org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object, int)
449 */
450 @Override
451 protected void processAfterDeleteLine(View view, CollectionGroup collectionGroup, Object model, int lineIndex) {
452 super.processAfterDeleteLine(view, collectionGroup, model, lineIndex);
453
454 // Check for maintenance documents in edit but exclude notes
455 if (model instanceof MaintenanceForm && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceForm)model).getMaintenanceAction())
456 && !collectionGroup.getCollectionObjectClass().getName().equals(Note.class.getName())) {
457 MaintenanceForm maintenanceForm = (MaintenanceForm) model;
458 MaintenanceDocument document = maintenanceForm.getDocument();
459
460 // get the old object's collection
461 Collection<Object> oldCollection = ObjectPropertyUtils
462 .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
463 collectionGroup.getPropertyName());
464 try {
465 // Remove the object at lineIndex from the collection
466 oldCollection.remove(oldCollection.toArray()[lineIndex]);
467 } catch (Exception e) {
468 throw new RuntimeException("Unable to delete line instance for old maintenance object", e);
469 }
470 }
471 }
472
473 /**
474 * Retrieves the document number configured on this maintainable
475 *
476 * @return String document number
477 */
478 protected String getDocumentNumber() {
479 return this.documentNumber;
480 }
481
482 protected LookupService getLookupService() {
483 if (lookupService == null) {
484 lookupService = KRADServiceLocatorWeb.getLookupService();
485 }
486 return this.lookupService;
487 }
488
489 public void setLookupService(LookupService lookupService) {
490 this.lookupService = lookupService;
491 }
492
493 protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
494 if (dataObjectAuthorizationService == null) {
495 this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
496 }
497 return dataObjectAuthorizationService;
498 }
499
500 public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
501 this.dataObjectAuthorizationService = dataObjectAuthorizationService;
502 }
503
504 protected DataObjectMetaDataService getDataObjectMetaDataService() {
505 if (dataObjectMetaDataService == null) {
506 this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
507 }
508 return dataObjectMetaDataService;
509 }
510
511 public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
512 this.dataObjectMetaDataService = dataObjectMetaDataService;
513 }
514
515 public DocumentDictionaryService getDocumentDictionaryService() {
516 if (documentDictionaryService == null) {
517 this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
518 }
519 return documentDictionaryService;
520 }
521
522 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
523 this.documentDictionaryService = documentDictionaryService;
524 }
525
526 protected EncryptionService getEncryptionService() {
527 if (encryptionService == null) {
528 encryptionService = CoreApiServiceLocator.getEncryptionService();
529 }
530 return encryptionService;
531 }
532
533 public void setEncryptionService(EncryptionService encryptionService) {
534 this.encryptionService = encryptionService;
535 }
536
537 protected BusinessObjectService getBusinessObjectService() {
538 if (businessObjectService == null) {
539 businessObjectService = KRADServiceLocator.getBusinessObjectService();
540 }
541 return businessObjectService;
542 }
543
544 public void setBusinessObjectService(BusinessObjectService businessObjectService) {
545 this.businessObjectService = businessObjectService;
546 }
547
548 protected MaintenanceDocumentService getMaintenanceDocumentService() {
549 if (maintenanceDocumentService == null) {
550 maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService();
551 }
552 return maintenanceDocumentService;
553 }
554
555 public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) {
556 this.maintenanceDocumentService = maintenanceDocumentService;
557 }
558 }