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 }