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