View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    * 
4    * Copyright 2005-2014 The Kuali Foundation
5    * 
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.kfs.coa.service.impl;
20  
21  import java.text.MessageFormat;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.commons.lang.StringUtils;
29  import org.apache.log4j.Logger;
30  import org.kuali.kfs.coa.businessobject.OrganizationReversion;
31  import org.kuali.kfs.coa.businessobject.OrganizationReversionCategory;
32  import org.kuali.kfs.coa.businessobject.OrganizationReversionDetail;
33  import org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService;
34  import org.kuali.kfs.sys.KFSKeyConstants;
35  import org.kuali.rice.core.api.config.property.ConfigurationService;
36  import org.kuali.rice.krad.bo.DocumentHeader;
37  import org.kuali.rice.krad.bo.Note;
38  import org.kuali.rice.krad.bo.PersistableBusinessObject;
39  import org.kuali.rice.krad.service.BusinessObjectService;
40  import org.kuali.rice.krad.service.DocumentHeaderService;
41  import org.kuali.rice.krad.service.NoteService;
42  import org.kuali.rice.krad.util.GlobalVariables;
43  import org.kuali.rice.krad.util.ObjectUtils;
44  
45  /**
46   * The default implementation of the OrganizationReversionDetailTrickleDownService
47   */
48  public class OrganizationReversionDetailTrickleDownInactivationServiceImpl implements OrganizationReversionDetailTrickleDownInactivationService {
49      private static final Logger LOG = Logger.getLogger(OrganizationReversionDetailTrickleDownInactivationServiceImpl.class);
50      protected NoteService noteService;
51      protected ConfigurationService kualiConfigurationService;
52      protected BusinessObjectService businessObjectService;
53      protected DocumentHeaderService documentHeaderService;
54      
55      /**
56       * @see org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService#trickleDownInactiveOrganizationReversionDetails(org.kuali.kfs.coa.businessobject.OrganizationReversion, java.lang.String)
57       */
58      public void trickleDownInactiveOrganizationReversionDetails(OrganizationReversion organizationReversion, String documentNumber) {
59          organizationReversion.refreshReferenceObject("organizationReversionDetail");
60          trickleDownInactivations(organizationReversion.getOrganizationReversionDetail(), documentNumber);
61      }
62  
63      /**
64       * @see org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService#trickleDownInactiveOrganizationReversionDetails(org.kuali.kfs.coa.businessobject.OrganizationReversionCategory, java.lang.String)
65       */
66      public void trickleDownInactiveOrganizationReversionDetails(OrganizationReversionCategory organizationReversionCategory, String documentNumber) {
67          Map<String, Object> fieldValues = new HashMap<String, Object>();
68          fieldValues.put("organizationReversionCategoryCode", organizationReversionCategory.getOrganizationReversionCategoryCode());
69          Collection orgReversionDetails = businessObjectService.findMatching(OrganizationReversionDetail.class, fieldValues);
70          
71          List<OrganizationReversionDetail> organizationReversionDetailList = new ArrayList<OrganizationReversionDetail>();
72          for (Object orgRevDetailAsObject : orgReversionDetails) {
73              organizationReversionDetailList.add((OrganizationReversionDetail)orgRevDetailAsObject);
74          }
75          trickleDownInactivations(organizationReversionDetailList, documentNumber);
76      }
77      
78      /**
79       * @see org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService#trickleDownActiveOrganizationReversionDetails(org.kuali.kfs.coa.businessobject.OrganizationReversion, java.lang.String)
80       */
81      public void trickleDownActiveOrganizationReversionDetails(OrganizationReversion organizationReversion, String documentNumber) {
82          organizationReversion.refreshReferenceObject("organizationReversionDetail");
83          trickleDownActivations(organizationReversion.getOrganizationReversionDetail(), documentNumber);
84      }
85  
86      /**
87       * @see org.kuali.kfs.coa.service.OrganizationReversionDetailTrickleDownInactivationService#trickleDownActiveOrganizationReversionDetails(org.kuali.kfs.coa.businessobject.OrganizationReversionCategory, java.lang.String)
88       */
89      public void trickleDownActiveOrganizationReversionDetails(OrganizationReversionCategory organizationReversionCategory, String documentNumber) {
90          Map<String, Object> fieldValues = new HashMap<String, Object>();
91          fieldValues.put("organizationReversionCategoryCode", organizationReversionCategory.getOrganizationReversionCategoryCode());
92          Collection orgReversionDetails = businessObjectService.findMatching(OrganizationReversionDetail.class, fieldValues);
93          
94          List<OrganizationReversionDetail> organizationReversionDetailList = new ArrayList<OrganizationReversionDetail>();
95          for (Object orgRevDetailAsObject : orgReversionDetails) {
96              organizationReversionDetailList.add((OrganizationReversionDetail)orgRevDetailAsObject);
97          }
98          trickleDownActivations(organizationReversionDetailList, documentNumber);
99      }
100 
101     /**
102      * The method which actually does the work of inactivating the details
103      * @param organizationReversionDetails the details to inactivate
104      * @param documentNumber the document number which has the inactivations as part of it
105      * @return an inactivation status object which will help us save notes
106      */
107     protected void trickleDownInactivations(List<OrganizationReversionDetail> organizationReversionDetails, String documentNumber) {
108         TrickleDownStatus status = new TrickleDownStatus(KFSKeyConstants.ORGANIZATION_REVERSION_DETAIL_TRICKLE_DOWN_INACTIVATION, KFSKeyConstants.ORGANIZATION_REVERSION_DETAIL_TRICKLE_DOWN_INACTIVATION_ERROR_DURING_PERSISTENCE);
109 
110         if (!ObjectUtils.isNull(organizationReversionDetails) && !organizationReversionDetails.isEmpty()) {
111             for (OrganizationReversionDetail detail : organizationReversionDetails) {
112                 if (detail.isActive()) {
113                     detail.setActive(false);
114                     try {
115                         businessObjectService.save(detail);
116                         status.addOrganizationReversionDetail(detail);
117                     }
118                     catch (RuntimeException re) {
119                         LOG.error("Unable to trickle-down inactivate sub-account " + detail.toString(), re);
120                         status.addErrorPersistingOrganizationReversionDetail(detail);
121                     }
122                 }
123             }
124         }
125         
126         status.saveSuccesfullyChangedNotes(documentNumber);
127         status.saveErrorNotes(documentNumber);
128     }
129     
130     /**
131      * The method which actually does the work of activating the details
132      * @param organizationReversionDetails the details to inactivate
133      * @param documentNumber the document number which has the inactivations as part of it
134      * @return an inactivation status object which will help us save notes
135      */
136     protected void trickleDownActivations(List<OrganizationReversionDetail> organizationReversionDetails, String documentNumber) {
137         TrickleDownStatus status = new TrickleDownStatus(KFSKeyConstants.ORGANIZATION_REVERSION_DETAIL_TRICKLE_DOWN_ACTIVATION, KFSKeyConstants.ORGANIZATION_REVERSION_DETAIL_TRICKLE_DOWN_ACTIVATION_ERROR_DURING_PERSISTENCE);
138                 
139         if (!ObjectUtils.isNull(organizationReversionDetails) && !organizationReversionDetails.isEmpty()) {
140             for (OrganizationReversionDetail detail : organizationReversionDetails) {
141                 if (!detail.isActive() && allowActivation(detail)) {
142                     detail.setActive(true);
143                     try {
144                         businessObjectService.save(detail);
145                         status.addOrganizationReversionDetail(detail);
146                     }
147                     catch (RuntimeException re) {
148                         LOG.error("Unable to trickle-down inactivate sub-account " + detail.toString(), re);
149                         status.addErrorPersistingOrganizationReversionDetail(detail);
150                     }
151                 }
152             }
153         }
154         
155         status.saveSuccesfullyChangedNotes(documentNumber);
156         status.saveErrorNotes(documentNumber);
157     }
158     
159     /**
160      * Determines whether the given organization reversion detail can be activated: ie, that both its owning OrganizationReversion and its related
161      * OrganizationReversionCategory are both active
162      * @param detail the detail to check
163      * @return true if the detail can be activated, false otherwise
164      */
165     protected boolean allowActivation(OrganizationReversionDetail detail) {
166         boolean result = true;
167         if (!ObjectUtils.isNull(detail.getOrganizationReversion())) {
168             result &= detail.getOrganizationReversion().isActive();
169         }
170         if (!ObjectUtils.isNull(detail.getOrganizationReversionCategory())) {
171             result &= detail.getOrganizationReversionCategory().isActive();
172         }
173         return result;
174     }
175 
176     /**
177      * Inner class to keep track of what organization reversions were inactivated and which
178      * had errors when the persisting of the inactivation was attempted
179      */
180     protected class TrickleDownStatus {
181         private List<OrganizationReversionDetail> organizationReversionDetails;
182         private List<OrganizationReversionDetail> errorPersistingOrganizationReversionDetails;
183         private String successfullyChangedOrganizationReversionDetailsMessageKey;
184         private String erroredOutOrganizationReversionDetailsMessageKey;
185         
186         /**
187          * Constructs a OrganizationReversionDetailTrickleDownInactivationServiceImpl
188          */
189         public TrickleDownStatus(String successfullyChangedOrganizationReversionDetailsMessageKey, String erroredOutOrganizationReversionDetailsMessageKey) {
190             organizationReversionDetails = new ArrayList<OrganizationReversionDetail>();
191             errorPersistingOrganizationReversionDetails = new ArrayList<OrganizationReversionDetail>();
192             this.successfullyChangedOrganizationReversionDetailsMessageKey = successfullyChangedOrganizationReversionDetailsMessageKey;
193             this.erroredOutOrganizationReversionDetailsMessageKey = erroredOutOrganizationReversionDetailsMessageKey;
194         }
195         
196         /**
197          * Adds an organization reversion detail which had a successfully persisted activation to the message list
198          * @param organizationReversionDetail the detail to add to the list
199          */
200         public void addOrganizationReversionDetail(OrganizationReversionDetail organizationReversionDetail) {
201             organizationReversionDetails.add(organizationReversionDetail);
202         }
203         
204         /**
205          * Adds an organization reversion detail which could not successful persist its activation to the error message list
206          * @param organizationReversionDetail the detail to add to the list
207          */
208         public void addErrorPersistingOrganizationReversionDetail(OrganizationReversionDetail organizationReversionDetail) {
209             errorPersistingOrganizationReversionDetails.add(organizationReversionDetail);
210         }
211         
212         /**
213          * @return the number of details we want per note
214          */
215         protected int getDetailsPerNote() {
216             return 20;
217         }
218         
219         /**
220          * Builds a List of Notes out of a list of OrganizationReversionDescriptions
221          * @param messageKey the key of the note text in ApplicationResources.properties
222          * @param noteParent the thing to stick the note on
223          * @param organizationReversionDetails the List of OrganizationReversionDetails to make notes about
224          * @return a List of Notes
225          */
226         protected List<Note> generateNotes(String messageKey, PersistableBusinessObject noteParent, List<OrganizationReversionDetail> organizationReversionDetails) {
227             List<Note> notes = new ArrayList<Note>();
228             List<String> organizationReversionDetailsDescriptions = generateOrganizationReversionDetailsForNotes(organizationReversionDetails);
229             Note noteTemplate = new Note();
230             for (String description : organizationReversionDetailsDescriptions) {
231                 if (!StringUtils.isBlank(description)) {
232                     notes.add(buildNote(description, messageKey, noteTemplate, noteParent));
233                 }
234             }
235             return notes;
236         }
237         
238         /**
239          * Builds a note
240          * @param description a description to put into the message of the note
241          * @param messageKey the key of the note text in ApplicationResources.properties
242          * @param noteTemplate the template for the note
243          * @param noteParent the thing to stick the note on
244          * @return the built note
245          */
246         protected Note buildNote(String description, String messageKey, Note noteTemplate, PersistableBusinessObject noteParent) {
247             Note note = null;
248             try {
249                 final String noteTextTemplate = kualiConfigurationService.getPropertyValueAsString(messageKey);
250                 final String noteText = MessageFormat.format(noteTextTemplate, description);
251                 note = noteService.createNote(noteTemplate, noteParent, GlobalVariables.getUserSession().getPrincipalId());
252                 note.setNoteText(noteText);
253             }
254             catch (Exception e) {
255                 // noteService.createNote throws *Exception*???
256                 // weak!!
257                 throw new RuntimeException("Cannot create note", e);
258             }
259             return note;
260         }
261         
262         /**
263          * Builds organization reverion detail descriptions to populate notes
264          * @param organizationReversionDetails the list of details to convert to notes
265          * @return a List of notes
266          */
267         protected List<String> generateOrganizationReversionDetailsForNotes(List<OrganizationReversionDetail> organizationReversionDetails) {
268             List<String> orgRevDetailDescriptions = new ArrayList<String>();
269             
270             if (organizationReversionDetails.size() > 0) {
271                 StringBuilder description = new StringBuilder();
272                 description.append(getOrganizationReversionDetailDescription(organizationReversionDetails.get(0)));
273                 
274                 int count = 1;
275                 while (count < organizationReversionDetails.size()) {
276                     if (count % getDetailsPerNote() == 0) { // time for a new note
277                         orgRevDetailDescriptions.add(description.toString());
278                         description = new StringBuilder();
279                     } else {
280                         description.append(", ");
281                     }
282                     description.append(getOrganizationReversionDetailDescription(organizationReversionDetails.get(count)));
283                     count += 1;
284                 }
285                 
286                 // add the last description
287                 orgRevDetailDescriptions.add(description.toString());
288             }
289             
290             return orgRevDetailDescriptions;
291         }
292         
293         /**
294          * Beautifully and eloquently describes an organization reversion detail
295          * @param organizationReversionDetail the organization reversion detail to describe
296          * @return the funny, heart-breaking, and ultimately inspiring resultant description
297          */
298         protected String getOrganizationReversionDetailDescription(OrganizationReversionDetail organizationReversionDetail) {
299             return organizationReversionDetail.getChartOfAccountsCode() + " - " + organizationReversionDetail.getOrganizationCode() + " Category: " + organizationReversionDetail.getOrganizationReversionCategoryCode();
300         }
301         
302         /**
303          * Saves notes to a document
304          * @param organizationReversionDetails the details to make notes about
305          * @param messageKey the message key of the text of the note
306          * @param documentNumber the document number to write to
307          */
308         protected void saveAllNotes(List<OrganizationReversionDetail> organizationReversionDetails, String messageKey, String documentNumber) {
309             DocumentHeader noteParent = documentHeaderService.getDocumentHeaderById(documentNumber);
310             List<Note> notes = generateNotes(messageKey, noteParent, organizationReversionDetails);
311             noteService.saveNoteList(notes);
312         }
313         
314         /**
315          * Adds all the notes about successful inactivations
316          * @param documentNumber document number to save them to
317          */
318         public void saveSuccesfullyChangedNotes(String documentNumber) {
319             saveAllNotes(organizationReversionDetails, successfullyChangedOrganizationReversionDetailsMessageKey, documentNumber);
320         }
321         
322         /**
323          * Adds all the notes about inactivations which couldn't be saved
324          * @param documentNumber the document number to save them to
325          */
326         public void saveErrorNotes(String documentNumber) {
327             saveAllNotes(errorPersistingOrganizationReversionDetails, erroredOutOrganizationReversionDetailsMessageKey, documentNumber);
328         }
329 
330         /**
331          * Sets the erroredOutOrganizationReversionDetailsMessageKey attribute value.
332          * @param erroredOutOrganizationReversionDetailsMessageKey The erroredOutOrganizationReversionDetailsMessageKey to set.
333          */
334         public void setErroredOutOrganizationReversionDetailsMessageKey(String erroredOutOrganizationReversionDetailsMessageKey) {
335             this.erroredOutOrganizationReversionDetailsMessageKey = erroredOutOrganizationReversionDetailsMessageKey;
336         }
337 
338         /**
339          * Sets the successfullyChangedOrganizationReversionDetailsMessageKey attribute value.
340          * @param successfullyChangedOrganizationReversionDetailsMessageKey The successfullyChangedOrganizationReversionDetailsMessageKey to set.
341          */
342         public void setSuccessfullyChangedOrganizationReversionDetailsMessageKey(String successfullyChangedOrganizationReversionDetailsMessageKey) {
343             this.successfullyChangedOrganizationReversionDetailsMessageKey = successfullyChangedOrganizationReversionDetailsMessageKey;
344         }
345     }
346 
347     /**
348      * Gets the kualiConfigurationService attribute. 
349      * @return Returns the kualiConfigurationService.
350      */
351     public ConfigurationService getConfigurationService() {
352         return kualiConfigurationService;
353     }
354 
355     /**
356      * Sets the kualiConfigurationService attribute value.
357      * @param kualiConfigurationService The kualiConfigurationService to set.
358      */
359     public void setConfigurationService(ConfigurationService kualiConfigurationService) {
360         this.kualiConfigurationService = kualiConfigurationService;
361     }
362 
363     /**
364      * Gets the noteService attribute. 
365      * @return Returns the noteService.
366      */
367     public NoteService getNoteService() {
368         return noteService;
369     }
370 
371     /**
372      * Sets the noteService attribute value.
373      * @param noteService The noteService to set.
374      */
375     public void setNoteService(NoteService noteService) {
376         this.noteService = noteService;
377     }
378 
379     /**
380      * Gets the businessObjectService attribute. 
381      * @return Returns the businessObjectService.
382      */
383     public BusinessObjectService getBusinessObjectService() {
384         return businessObjectService;
385     }
386 
387     /**
388      * Sets the businessObjectService attribute value.
389      * @param businessObjectService The businessObjectService to set.
390      */
391     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
392         this.businessObjectService = businessObjectService;
393     }
394 
395     /**
396      * Gets the documentHeaderService attribute. 
397      * @return Returns the documentHeaderService.
398      */
399     public DocumentHeaderService getDocumentHeaderService() {
400         return documentHeaderService;
401     }
402 
403     /**
404      * Sets the documentHeaderService attribute value.
405      * @param documentHeaderService The documentHeaderService to set.
406      */
407     public void setDocumentHeaderService(DocumentHeaderService documentHeaderService) {
408         this.documentHeaderService = documentHeaderService;
409     }
410 }