001 /*
002 * Copyright 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.kfs.coa.service.impl;
017
018 import java.text.MessageFormat;
019 import java.util.ArrayList;
020 import java.util.Collection;
021 import java.util.HashMap;
022 import java.util.List;
023 import java.util.Map;
024
025 import org.apache.commons.lang.StringUtils;
026 import org.apache.log4j.Logger;
027 import org.kuali.kfs.coa.businessobject.OrganizationReversion;
028 import org.kuali.kfs.coa.businessobject.OrganizationReversionCategory;
029 import org.kuali.kfs.coa.businessobject.OrganizationReversionDetail;
030 import org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService;
031 import org.kuali.kfs.sys.KFSKeyConstants;
032 import org.kuali.rice.kns.bo.DocumentHeader;
033 import org.kuali.rice.kns.bo.Note;
034 import org.kuali.rice.kns.bo.PersistableBusinessObject;
035 import org.kuali.rice.kns.service.BusinessObjectService;
036 import org.kuali.rice.kns.service.DocumentHeaderService;
037 import org.kuali.rice.kns.service.KualiConfigurationService;
038 import org.kuali.rice.kns.service.NoteService;
039 import org.kuali.rice.kns.util.ObjectUtils;
040
041 /**
042 * The default implementation of the OrganizationReversionDetailTrickleDownService
043 */
044 public class OrganizationReversionDetailTrickleDownInactivationServiceImpl implements OrganizationReversionDetailTrickleDownInactivationService {
045 private static final Logger LOG = Logger.getLogger(OrganizationReversionDetailTrickleDownInactivationServiceImpl.class);
046 protected NoteService noteService;
047 protected KualiConfigurationService kualiConfigurationService;
048 protected BusinessObjectService businessObjectService;
049 protected DocumentHeaderService documentHeaderService;
050
051 /**
052 * @see org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService#trickleDownInactiveOrganizationReversionDetails(org.kuali.kfs.coa.businessobject.OrganizationReversion, java.lang.String)
053 */
054 public void trickleDownInactiveOrganizationReversionDetails(OrganizationReversion organizationReversion, String documentNumber) {
055 organizationReversion.refreshReferenceObject("organizationReversionDetail");
056 trickleDownInactivations(organizationReversion.getOrganizationReversionDetail(), documentNumber);
057 }
058
059 /**
060 * @see org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService#trickleDownInactiveOrganizationReversionDetails(org.kuali.kfs.coa.businessobject.OrganizationReversionCategory, java.lang.String)
061 */
062 public void trickleDownInactiveOrganizationReversionDetails(OrganizationReversionCategory organizationReversionCategory, String documentNumber) {
063 Map<String, Object> fieldValues = new HashMap<String, Object>();
064 fieldValues.put("organizationReversionCategoryCode", organizationReversionCategory.getOrganizationReversionCategoryCode());
065 Collection orgReversionDetails = businessObjectService.findMatching(OrganizationReversionDetail.class, fieldValues);
066
067 List<OrganizationReversionDetail> organizationReversionDetailList = new ArrayList<OrganizationReversionDetail>();
068 for (Object orgRevDetailAsObject : orgReversionDetails) {
069 organizationReversionDetailList.add((OrganizationReversionDetail)orgRevDetailAsObject);
070 }
071 trickleDownInactivations(organizationReversionDetailList, documentNumber);
072 }
073
074 /**
075 * @see org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService#trickleDownActiveOrganizationReversionDetails(org.kuali.kfs.coa.businessobject.OrganizationReversion, java.lang.String)
076 */
077 public void trickleDownActiveOrganizationReversionDetails(OrganizationReversion organizationReversion, String documentNumber) {
078 organizationReversion.refreshReferenceObject("organizationReversionDetail");
079 trickleDownActivations(organizationReversion.getOrganizationReversionDetail(), documentNumber);
080 }
081
082 /**
083 * @see org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService#trickleDownActiveOrganizationReversionDetails(org.kuali.kfs.coa.businessobject.OrganizationReversionCategory, java.lang.String)
084 */
085 public void trickleDownActiveOrganizationReversionDetails(OrganizationReversionCategory organizationReversionCategory, String documentNumber) {
086 Map<String, Object> fieldValues = new HashMap<String, Object>();
087 fieldValues.put("organizationReversionCategoryCode", organizationReversionCategory.getOrganizationReversionCategoryCode());
088 Collection orgReversionDetails = businessObjectService.findMatching(OrganizationReversionDetail.class, fieldValues);
089
090 List<OrganizationReversionDetail> organizationReversionDetailList = new ArrayList<OrganizationReversionDetail>();
091 for (Object orgRevDetailAsObject : orgReversionDetails) {
092 organizationReversionDetailList.add((OrganizationReversionDetail)orgRevDetailAsObject);
093 }
094 trickleDownActivations(organizationReversionDetailList, documentNumber);
095 }
096
097 /**
098 * The method which actually does the work of inactivating the details
099 * @param organizationReversionDetails the details to inactivate
100 * @param documentNumber the document number which has the inactivations as part of it
101 * @return an inactivation status object which will help us save notes
102 */
103 protected void trickleDownInactivations(List<OrganizationReversionDetail> organizationReversionDetails, String documentNumber) {
104 TrickleDownStatus status = new TrickleDownStatus(KFSKeyConstants.ORGANIZATION_REVERSION_DETAIL_TRICKLE_DOWN_INACTIVATION, KFSKeyConstants.ORGANIZATION_REVERSION_DETAIL_TRICKLE_DOWN_INACTIVATION_ERROR_DURING_PERSISTENCE);
105
106 if (!ObjectUtils.isNull(organizationReversionDetails) && !organizationReversionDetails.isEmpty()) {
107 for (OrganizationReversionDetail detail : organizationReversionDetails) {
108 if (detail.isActive()) {
109 detail.setActive(false);
110 try {
111 businessObjectService.save(detail);
112 status.addOrganizationReversionDetail(detail);
113 }
114 catch (RuntimeException re) {
115 LOG.error("Unable to trickle-down inactivate sub-account " + detail.toString(), re);
116 status.addErrorPersistingOrganizationReversionDetail(detail);
117 }
118 }
119 }
120 }
121
122 status.saveSuccesfullyChangedNotes(documentNumber);
123 status.saveErrorNotes(documentNumber);
124 }
125
126 /**
127 * The method which actually does the work of activating the details
128 * @param organizationReversionDetails the details to inactivate
129 * @param documentNumber the document number which has the inactivations as part of it
130 * @return an inactivation status object which will help us save notes
131 */
132 protected void trickleDownActivations(List<OrganizationReversionDetail> organizationReversionDetails, String documentNumber) {
133 TrickleDownStatus status = new TrickleDownStatus(KFSKeyConstants.ORGANIZATION_REVERSION_DETAIL_TRICKLE_DOWN_ACTIVATION, KFSKeyConstants.ORGANIZATION_REVERSION_DETAIL_TRICKLE_DOWN_ACTIVATION_ERROR_DURING_PERSISTENCE);
134
135 if (!ObjectUtils.isNull(organizationReversionDetails) && !organizationReversionDetails.isEmpty()) {
136 for (OrganizationReversionDetail detail : organizationReversionDetails) {
137 if (!detail.isActive() && allowActivation(detail)) {
138 detail.setActive(true);
139 try {
140 businessObjectService.save(detail);
141 status.addOrganizationReversionDetail(detail);
142 }
143 catch (RuntimeException re) {
144 LOG.error("Unable to trickle-down inactivate sub-account " + detail.toString(), re);
145 status.addErrorPersistingOrganizationReversionDetail(detail);
146 }
147 }
148 }
149 }
150
151 status.saveSuccesfullyChangedNotes(documentNumber);
152 status.saveErrorNotes(documentNumber);
153 }
154
155 /**
156 * Determines whether the given organization reversion detail can be activated: ie, that both its owning OrganizationReversion and its related
157 * OrganizationReversionCategory are both active
158 * @param detail the detail to check
159 * @return true if the detail can be activated, false otherwise
160 */
161 protected boolean allowActivation(OrganizationReversionDetail detail) {
162 boolean result = true;
163 if (!ObjectUtils.isNull(detail.getOrganizationReversion())) {
164 result &= detail.getOrganizationReversion().isActive();
165 }
166 if (!ObjectUtils.isNull(detail.getOrganizationReversionCategory())) {
167 result &= detail.getOrganizationReversionCategory().isActive();
168 }
169 return result;
170 }
171
172 /**
173 * Inner class to keep track of what organization reversions were inactivated and which
174 * had errors when the persisting of the inactivation was attempted
175 */
176 protected class TrickleDownStatus {
177 private List<OrganizationReversionDetail> organizationReversionDetails;
178 private List<OrganizationReversionDetail> errorPersistingOrganizationReversionDetails;
179 private String successfullyChangedOrganizationReversionDetailsMessageKey;
180 private String erroredOutOrganizationReversionDetailsMessageKey;
181
182 /**
183 * Constructs a OrganizationReversionDetailTrickleDownInactivationServiceImpl
184 */
185 public TrickleDownStatus(String successfullyChangedOrganizationReversionDetailsMessageKey, String erroredOutOrganizationReversionDetailsMessageKey) {
186 organizationReversionDetails = new ArrayList<OrganizationReversionDetail>();
187 errorPersistingOrganizationReversionDetails = new ArrayList<OrganizationReversionDetail>();
188 this.successfullyChangedOrganizationReversionDetailsMessageKey = successfullyChangedOrganizationReversionDetailsMessageKey;
189 this.erroredOutOrganizationReversionDetailsMessageKey = erroredOutOrganizationReversionDetailsMessageKey;
190 }
191
192 /**
193 * Adds an organization reversion detail which had a successfully persisted activation to the message list
194 * @param organizationReversionDetail the detail to add to the list
195 */
196 public void addOrganizationReversionDetail(OrganizationReversionDetail organizationReversionDetail) {
197 organizationReversionDetails.add(organizationReversionDetail);
198 }
199
200 /**
201 * Adds an organization reversion detail which could not successful persist its activation to the error message list
202 * @param organizationReversionDetail the detail to add to the list
203 */
204 public void addErrorPersistingOrganizationReversionDetail(OrganizationReversionDetail organizationReversionDetail) {
205 errorPersistingOrganizationReversionDetails.add(organizationReversionDetail);
206 }
207
208 /**
209 * @return the number of details we want per note
210 */
211 protected int getDetailsPerNote() {
212 return 20;
213 }
214
215 /**
216 * Builds a List of Notes out of a list of OrganizationReversionDescriptions
217 * @param messageKey the key of the note text in ApplicationResources.properties
218 * @param noteParent the thing to stick the note on
219 * @param organizationReversionDetails the List of OrganizationReversionDetails to make notes about
220 * @return a List of Notes
221 */
222 protected List<Note> generateNotes(String messageKey, PersistableBusinessObject noteParent, List<OrganizationReversionDetail> organizationReversionDetails) {
223 List<Note> notes = new ArrayList<Note>();
224 List<String> organizationReversionDetailsDescriptions = generateOrganizationReversionDetailsForNotes(organizationReversionDetails);
225 Note noteTemplate = new Note();
226 for (String description : organizationReversionDetailsDescriptions) {
227 if (!StringUtils.isBlank(description)) {
228 notes.add(buildNote(description, messageKey, noteTemplate, noteParent));
229 }
230 }
231 return notes;
232 }
233
234 /**
235 * Builds a note
236 * @param description a description to put into the message of the note
237 * @param messageKey the key of the note text in ApplicationResources.properties
238 * @param noteTemplate the template for the note
239 * @param noteParent the thing to stick the note on
240 * @return the built note
241 */
242 protected Note buildNote(String description, String messageKey, Note noteTemplate, PersistableBusinessObject noteParent) {
243 Note note = null;
244 try {
245 final String noteTextTemplate = kualiConfigurationService.getPropertyString(messageKey);
246 final String noteText = MessageFormat.format(noteTextTemplate, description);
247 note = noteService.createNote(noteTemplate, noteParent);
248 note.setNoteText(noteText);
249 }
250 catch (Exception e) {
251 // noteService.createNote throws *Exception*???
252 // weak!!
253 throw new RuntimeException("Cannot create note", e);
254 }
255 return note;
256 }
257
258 /**
259 * Builds organization reverion detail descriptions to populate notes
260 * @param organizationReversionDetails the list of details to convert to notes
261 * @return a List of notes
262 */
263 protected List<String> generateOrganizationReversionDetailsForNotes(List<OrganizationReversionDetail> organizationReversionDetails) {
264 List<String> orgRevDetailDescriptions = new ArrayList<String>();
265
266 if (organizationReversionDetails.size() > 0) {
267 StringBuilder description = new StringBuilder();
268 description.append(getOrganizationReversionDetailDescription(organizationReversionDetails.get(0)));
269
270 int count = 1;
271 while (count < organizationReversionDetails.size()) {
272 if (count % getDetailsPerNote() == 0) { // time for a new note
273 orgRevDetailDescriptions.add(description.toString());
274 description = new StringBuilder();
275 } else {
276 description.append(", ");
277 }
278 description.append(getOrganizationReversionDetailDescription(organizationReversionDetails.get(count)));
279 count += 1;
280 }
281
282 // add the last description
283 orgRevDetailDescriptions.add(description.toString());
284 }
285
286 return orgRevDetailDescriptions;
287 }
288
289 /**
290 * Beautifully and eloquently describes an organization reversion detail
291 * @param organizationReversionDetail the organization reversion detail to describe
292 * @return the funny, heart-breaking, and ultimately inspiring resultant description
293 */
294 protected String getOrganizationReversionDetailDescription(OrganizationReversionDetail organizationReversionDetail) {
295 return organizationReversionDetail.getChartOfAccountsCode() + " - " + organizationReversionDetail.getOrganizationCode() + " Category: " + organizationReversionDetail.getOrganizationReversionCategoryCode();
296 }
297
298 /**
299 * Saves notes to a document
300 * @param organizationReversionDetails the details to make notes about
301 * @param messageKey the message key of the text of the note
302 * @param documentNumber the document number to write to
303 */
304 protected void saveAllNotes(List<OrganizationReversionDetail> organizationReversionDetails, String messageKey, String documentNumber) {
305 DocumentHeader noteParent = documentHeaderService.getDocumentHeaderById(documentNumber);
306 List<Note> notes = generateNotes(messageKey, noteParent, organizationReversionDetails);
307 noteService.saveNoteList(notes);
308 }
309
310 /**
311 * Adds all the notes about successful inactivations
312 * @param documentNumber document number to save them to
313 */
314 public void saveSuccesfullyChangedNotes(String documentNumber) {
315 saveAllNotes(organizationReversionDetails, successfullyChangedOrganizationReversionDetailsMessageKey, documentNumber);
316 }
317
318 /**
319 * Adds all the notes about inactivations which couldn't be saved
320 * @param documentNumber the document number to save them to
321 */
322 public void saveErrorNotes(String documentNumber) {
323 saveAllNotes(errorPersistingOrganizationReversionDetails, erroredOutOrganizationReversionDetailsMessageKey, documentNumber);
324 }
325
326 /**
327 * Sets the erroredOutOrganizationReversionDetailsMessageKey attribute value.
328 * @param erroredOutOrganizationReversionDetailsMessageKey The erroredOutOrganizationReversionDetailsMessageKey to set.
329 */
330 public void setErroredOutOrganizationReversionDetailsMessageKey(String erroredOutOrganizationReversionDetailsMessageKey) {
331 this.erroredOutOrganizationReversionDetailsMessageKey = erroredOutOrganizationReversionDetailsMessageKey;
332 }
333
334 /**
335 * Sets the successfullyChangedOrganizationReversionDetailsMessageKey attribute value.
336 * @param successfullyChangedOrganizationReversionDetailsMessageKey The successfullyChangedOrganizationReversionDetailsMessageKey to set.
337 */
338 public void setSuccessfullyChangedOrganizationReversionDetailsMessageKey(String successfullyChangedOrganizationReversionDetailsMessageKey) {
339 this.successfullyChangedOrganizationReversionDetailsMessageKey = successfullyChangedOrganizationReversionDetailsMessageKey;
340 }
341 }
342
343 /**
344 * Gets the kualiConfigurationService attribute.
345 * @return Returns the kualiConfigurationService.
346 */
347 public KualiConfigurationService getKualiConfigurationService() {
348 return kualiConfigurationService;
349 }
350
351 /**
352 * Sets the kualiConfigurationService attribute value.
353 * @param kualiConfigurationService The kualiConfigurationService to set.
354 */
355 public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) {
356 this.kualiConfigurationService = kualiConfigurationService;
357 }
358
359 /**
360 * Gets the noteService attribute.
361 * @return Returns the noteService.
362 */
363 public NoteService getNoteService() {
364 return noteService;
365 }
366
367 /**
368 * Sets the noteService attribute value.
369 * @param noteService The noteService to set.
370 */
371 public void setNoteService(NoteService noteService) {
372 this.noteService = noteService;
373 }
374
375 /**
376 * Gets the businessObjectService attribute.
377 * @return Returns the businessObjectService.
378 */
379 public BusinessObjectService getBusinessObjectService() {
380 return businessObjectService;
381 }
382
383 /**
384 * Sets the businessObjectService attribute value.
385 * @param businessObjectService The businessObjectService to set.
386 */
387 public void setBusinessObjectService(BusinessObjectService businessObjectService) {
388 this.businessObjectService = businessObjectService;
389 }
390
391 /**
392 * Gets the documentHeaderService attribute.
393 * @return Returns the documentHeaderService.
394 */
395 public DocumentHeaderService getDocumentHeaderService() {
396 return documentHeaderService;
397 }
398
399 /**
400 * Sets the documentHeaderService attribute value.
401 * @param documentHeaderService The documentHeaderService to set.
402 */
403 public void setDocumentHeaderService(DocumentHeaderService documentHeaderService) {
404 this.documentHeaderService = documentHeaderService;
405 }
406 }