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.module.purap.util;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.Iterator;
021    import java.util.List;
022    
023    import org.kuali.rice.kns.bo.PersistableBusinessObject;
024    import org.kuali.rice.kns.util.ObjectUtils;
025    import org.kuali.rice.kns.util.OjbCollectionAware;
026    import org.springframework.orm.ObjectRetrievalFailureException;
027    
028    /**
029     * Helper object to deal with persisting collections.
030     */
031    public class PurApOjbCollectionHelper {
032        public final static int MAX_DEPTH = 2;
033    
034        /**
035         * OJB RemovalAwareLists do not survive through the response/request lifecycle. This method is a work-around to forcibly remove
036         * business objects that are found in Collections stored in the database but not in memory.
037         * 
038         * @param orig
039         * @param id
040         * @param template
041         */
042        public void processCollections(OjbCollectionAware template, PersistableBusinessObject orig, PersistableBusinessObject copy) {
043            processCollectionsRecurse(template, orig, copy, MAX_DEPTH);
044        }
045    
046        /**
047         * This method processes collections recursively up to the depth level specified
048         * 
049         * @param template
050         * @param orig
051         * @param copy
052         */
053        private void processCollectionsRecurse(OjbCollectionAware template, PersistableBusinessObject orig, PersistableBusinessObject copy, int depth) {
054            if (copy == null || depth < 1) {
055                return;
056            }
057    
058            List originalCollections = orig.buildListOfDeletionAwareLists();
059    
060            if (originalCollections != null && !originalCollections.isEmpty()) {
061                /*
062                 * Prior to being saved, the version in the database will not yet reflect any deleted collections. So, a freshly
063                 * retrieved version will contain objects that need to be removed:
064                 */
065                try {
066                    List copyCollections = copy.buildListOfDeletionAwareLists();
067                    int size = originalCollections.size();
068    
069                    if (copyCollections.size() != size) {
070                        throw new RuntimeException("size mismatch while attempting to process list of Collections to manage");
071                    }
072    
073                    for (int i = 0; i < size; i++) {
074                        Collection<PersistableBusinessObject> origSource = (Collection<PersistableBusinessObject>) originalCollections.get(i);
075                        Collection<PersistableBusinessObject> copySource = (Collection<PersistableBusinessObject>) copyCollections.get(i);
076                        List list = findUnwantedElements(copySource, origSource, template, depth - 1);
077                        cleanse(template, origSource, list);
078    
079                    }
080                }
081                catch (ObjectRetrievalFailureException orfe) {
082                    // object wasn't found, must be pre-save
083                }
084            }
085        }
086    
087        /**
088         * OJB RemovalAwareLists do not survive through the response/request lifecycle. This method is a work-around to forcibly remove
089         * business objects that are found in Collections stored in the database but not in memory.
090         * 
091         * @param orig
092         * @param id
093         * @param template
094         */
095        public void processCollections2(OjbCollectionAware template, PersistableBusinessObject orig, PersistableBusinessObject copy) {
096            // if copy is null this is the first time we are saving the object, don't have to worry about updating collections
097            if (copy == null) {
098                return;
099            }
100    
101            List originalCollections = orig.buildListOfDeletionAwareLists();
102    
103            if (originalCollections != null && !originalCollections.isEmpty()) {
104                /*
105                 * Prior to being saved, the version in the database will not yet reflect any deleted collections. So, a freshly
106                 * retrieved version will contain objects that need to be removed:
107                 */
108                try {
109                    List copyCollections = copy.buildListOfDeletionAwareLists();
110                    int size = originalCollections.size();
111    
112                    if (copyCollections.size() != size) {
113                        throw new RuntimeException("size mismatch while attempting to process list of Collections to manage");
114                    }
115    
116                    for (int i = 0; i < size; i++) {
117                        Collection origSource = (Collection) originalCollections.get(i);
118                        Collection copySource = (Collection) copyCollections.get(i);
119                        List list = findUnwantedElements(copySource, origSource, null, 0);
120                        cleanse(template, origSource, list);
121    
122                    }
123                }
124                catch (ObjectRetrievalFailureException orfe) {
125                    // object wasn't found, must be pre-save
126                }
127            }
128        }
129    
130        /**
131         * This method deletes unwanted objects from the database as well as from the given input List
132         * 
133         * @param origSource - list containing unwanted business objects
134         * @param unwantedItems - business objects to be permanently removed
135         * @param template
136         */
137        private void cleanse(OjbCollectionAware template, Collection origSource, List unwantedItems) {
138            if (unwantedItems.size() > 0) {
139                Iterator iter = unwantedItems.iterator();
140                while (iter.hasNext()) {
141                    template.getPersistenceBrokerTemplate().delete(iter.next());
142                }
143            }
144    
145        }
146    
147        /**
148         * This method identifies items in the first List that are not contained in the second List. It is similar to the (optional)
149         * java.util.List retainAll method.
150         * 
151         * @param fromList list from the database
152         * @param controlList list from the object
153         * @return true iff one or more items were removed
154         */
155        private List findUnwantedElements(Collection fromList, Collection controlList, OjbCollectionAware template, int depth) {
156            List toRemove = new ArrayList();
157    
158            Iterator iter = fromList.iterator();
159            while (iter.hasNext()) {
160                PersistableBusinessObject copyLine = (PersistableBusinessObject) iter.next();
161                
162                PersistableBusinessObject line = (PersistableBusinessObject) PurApObjectUtils.retrieveObjectWithIdentitcalKey(controlList, copyLine);
163                if (ObjectUtils.isNull(line)) {
164                    toRemove.add(copyLine);
165                }
166                else { // since we're not deleting try to recurse on this element
167                    processCollectionsRecurse(template, line, copyLine, depth);
168                }
169            }
170            return toRemove;
171        }
172    }