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    }