1 /**
2 * Copyright 2012 The Kuali Foundation
3 *
4 * Licensed under the the Educational Community License, Version 1.0
5 * (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at
7 *
8 * http://www.opensource.org/licenses/ecl1.php
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.kuali.student.r2.common.helper;
17
18 import java.util.ArrayList;
19 import java.util.LinkedHashMap;
20 import java.util.List;
21 import java.util.Map;
22
23 /**
24 *
25 * Helps perform merge operations that take place when an entity is loaded from
26 * the database and then updated from the dto content being provided through the
27 * service.
28 *
29 * @author ocleirig
30 *
31 * @param <E> The entity being merged into.
32 * @param <INFO> the dto providing the data to be merged.
33 */
34 public final class EntityMergeHelper<E, INFO> {
35
36 /**
37 * Options when merging the entity from the dto.
38 *
39 * @param <E>
40 * @param <INFO>
41 */
42 public static interface EntityMergeOptions<E, INFO> {
43
44 /**
45 * Extracts the id from the entity object.
46 *
47 * @param entity the entity.
48 * @return the database id for the entity.
49 */
50 public String getEntityId(E entity);
51
52 /**
53 * Extracts the id from the dto info object.
54 *
55 * @param info the dto,
56 * @return the database id encoded in the dto.
57 */
58 public String getInfoId(INFO info);
59
60 /**
61 * Merges the dto info into the entity.
62 *
63 * @param entity the target entity.
64 * @param info the source dto
65 * @return the list of objects that should be detatched due to the merge.
66 */
67 public List<Object> merge(E entity, INFO info);
68
69 /**
70 * Creates a new instance of the target entity.
71 *
72 * @param info the source dto.
73 * @return the new entity.
74 */
75 public E create(INFO info);
76
77 }
78
79 /**
80 * Options for merging a list of strings into a list of entities.
81 *
82 * @param <E> The Entity object
83 */
84 public static interface StringMergeOptions<E> {
85
86 /**
87 * Extract a comparison key from the entity that will match the value of the string list items (for comparison purposes)
88 *
89 * @param entity
90 * @return the comnparison key.
91 */
92 public String getKey(E entity);
93
94 /**
95 * Create a new entry based on the string value provided.
96 *
97 * @param string
98 * @return the new entity.
99 */
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 }