001    /**
002     * Copyright 2012 The Kuali Foundation
003     *
004     * Licensed under the the Educational Community License, Version 1.0
005     * (the "License"); you may not use this file except in compliance
006     * with the License.  You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl1.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.student.r2.common.helper;
017    
018    import java.util.ArrayList;
019    import java.util.LinkedHashMap;
020    import java.util.List;
021    import java.util.Map;
022    
023    /**
024     * 
025     * Helps perform merge operations that take place when an entity is loaded from
026     * the database and then updated from the dto content being provided through the
027     * service.
028     * 
029     * @author ocleirig
030     * 
031     * @param <E> The entity being merged into.
032     * @param <INFO> the dto providing the data to be merged.
033     */
034    public final class EntityMergeHelper<E, INFO> {
035    
036            /**
037             * Options when merging the entity from the dto.
038             *
039             * @param <E>
040             * @param <INFO>
041             */
042            public static interface EntityMergeOptions<E, INFO> {
043                    
044                    /**
045                     * Extracts the id from the entity object.
046                     * 
047                     * @param entity the entity.
048                     * @return the database id for the entity.
049                     */
050                    public String getEntityId(E entity);
051    
052                    /**
053                     * Extracts the id from the dto info object.
054                     * 
055                     * @param info the dto,
056                     * @return the database id encoded in the dto.
057                     */
058                    public String getInfoId(INFO info);
059    
060                    /**
061                     * Merges the dto info into the entity.
062                     * 
063                     * @param entity the target entity.
064                     * @param info the source dto
065                     * @return the list of objects that should be detatched due to the merge.
066                     */
067                    public List<Object> merge(E entity, INFO info);
068    
069                    /**
070                     * Creates a new instance of the target entity.
071                     * 
072                     * @param info the source dto.
073                     * @return the new entity.
074                     */
075                    public E create(INFO info);
076    
077            }
078    
079            /**
080         * Options for merging a list of strings into a list of entities.
081             * 
082             * @param <E> The Entity object
083             */
084            public static interface StringMergeOptions<E> {
085    
086                    /**
087                     * Extract a comparison key from the entity that will match the value of the string list items (for comparison purposes)
088                     * 
089                     * @param entity
090                     * @return the comnparison key.
091                     */
092                    public String getKey(E entity);
093    
094                    /**
095                     * Create a new entry based on the string value provided.
096                     * 
097                     * @param string
098                     * @return the new entity.
099                     */
100                    public E create(String value);
101    
102            }
103    
104            /**
105             * The results of a merge are the merged list and the list of orphaned data.
106             * 
107             * @param <E> the target entity.
108             */
109            public static final class EntityMergeResult<E> {
110    
111                    private final List<E> mergedList;
112    
113                    private final List<Object> orphanList;
114    
115                    public EntityMergeResult(List<E> mergedList, List<Object> orphanList) {
116                            super();
117                            this.mergedList = mergedList;
118                            this.orphanList = orphanList;
119                    }
120    
121                    public List<E> getMergedList() {
122                            return mergedList;
123                    }
124    
125                    public List<Object> getOrphanList() {
126                            return orphanList;
127                    }
128    
129            }
130    
131            
132            public EntityMergeHelper() {
133            }
134    
135            /**
136             * Merges a simple string list against a persisted list. We assume that if
137             * the string value does not exist then we can delete the item.
138             * 
139             * @param entityList the list of entities
140             * @param stringList the list of values
141             * @return the merge results.
142             */
143            public EntityMergeResult<E> mergeStringList(List<E> entityList,
144                            List<String> stringList, StringMergeOptions<E> options) {
145    
146                    /*
147                     * Steps:
148                     * 1. create existing map
149                     * 2. create new entries or remove existing from the map
150                     * 3. return new entity list and orphan list.
151                     */
152                    List<E> mergedList = new ArrayList<E>();
153    
154                    List<Object> orphanDataList = new ArrayList<Object>();
155    
156                    Map<String, E> existingEntityMap = new LinkedHashMap<String, E>();
157    
158                    if (entityList != null) {
159                            for (E e : entityList) {
160    
161                                    String key = options.getKey(e);
162    
163                                    existingEntityMap.put(key, e);
164    
165                            }
166                    }
167                    for (String string : stringList) {
168    
169                            E entity = existingEntityMap.get(string);
170    
171                            if (entity == null) {
172                                    // create
173    
174                                    entity = options.create(string);
175    
176                            } else {
177                                    // remove from existing map
178                                    existingEntityMap.remove(string);
179                            }
180    
181                            mergedList.add(entity);
182    
183                    }
184    
185                    orphanDataList.addAll(existingEntityMap.values());
186    
187                    return new EntityMergeResult<E>(mergedList, orphanDataList);
188            }
189    
190            /**
191             * Performs a Merge of the current entity list from the info list and
192             * options provided.
193             * 
194             * The results are the list of merged entities and a list of orphaned objects that can be deleted from the database.
195             * 
196             * @param entityList the target list of entities
197             * @param infoList the source list of dto info objects.
198             * @param options the logic for extracting the keys and creating/merging the entities.
199             * @return the results of the merge.
200             */
201            public EntityMergeResult<E> merge(List<E> entityList, List<INFO> infoList,
202                            EntityMergeOptions<E, INFO> options) {
203    
204                    List<Object> orphanDataList = new ArrayList<Object>();
205    
206                    Map<String, E> existingEntityMap = new LinkedHashMap<String, E>();
207    
208                    if (entityList != null) {
209                            for (E e : entityList) {
210    
211                                    String id = options.getEntityId(e);
212    
213                                    existingEntityMap.put(id, e);
214    
215                            }
216                    }
217    
218                    List<E> mergedList = new ArrayList<E>();
219    
220                    for (INFO info : infoList) {
221    
222                            String id = options.getInfoId(info);
223    
224                            E entity = null;
225                            if (existingEntityMap.containsKey(id)) {
226                                    // merge
227                                    entity = existingEntityMap.remove(id);
228    
229                                    orphanDataList.addAll(options.merge(entity, info));
230    
231                            } else {
232                                    // new entry
233    
234                                    entity = options.create(info);
235                            }
236    
237                            mergedList.add(entity);
238                    }
239    
240                    orphanDataList.addAll(existingEntityMap.values());
241    
242                    return new EntityMergeResult<E>(mergedList, orphanDataList);
243    
244            }
245    
246    }