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 }