001    package org.kuali.student.lum.course.service.assembler;
002    
003    import java.util.ArrayList;
004    import java.util.Collections;
005    import java.util.Comparator;
006    import java.util.HashMap;
007    import java.util.HashSet;
008    import java.util.List;
009    import java.util.Map;
010    import java.util.Set;
011    import java.util.Map.Entry;
012    
013    import org.kuali.student.common.assembly.BOAssembler;
014    import org.kuali.student.common.assembly.BaseDTOAssemblyNode;
015    import org.kuali.student.common.assembly.BaseDTOAssemblyNode.NodeOperation;
016    import org.kuali.student.common.assembly.data.AssemblyException;
017    import org.kuali.student.common.dto.DtoConstants;
018    import org.kuali.student.common.exceptions.DoesNotExistException;
019    import org.kuali.student.common.exceptions.InvalidParameterException;
020    import org.kuali.student.common.exceptions.MissingParameterException;
021    import org.kuali.student.common.exceptions.OperationFailedException;
022    import org.kuali.student.common.util.UUIDHelper;
023    import org.kuali.student.lum.course.dto.LoDisplayInfo;
024    import org.kuali.student.lum.lo.dto.LoCategoryInfo;
025    import org.kuali.student.lum.lo.dto.LoInfo;
026    import org.kuali.student.lum.lo.dto.LoLoRelationInfo;
027    import org.kuali.student.lum.lo.service.LearningObjectiveService;
028    
029    
030    public class LoAssembler implements BOAssembler<LoDisplayInfo, LoInfo> {
031    
032            private LearningObjectiveService loService;
033            
034            @Override
035            public LoDisplayInfo assemble(LoInfo lo, LoDisplayInfo loDisplayInfo,
036                            boolean shallowBuild) throws AssemblyException {
037                    
038                    LoDisplayInfo loDisplay = (null != loDisplayInfo) ? loDisplayInfo : new LoDisplayInfo();
039                    
040                    loDisplay.setLoInfo(lo);
041    
042                    if (!shallowBuild) {
043                            String loId = lo.getId();
044                            try {
045                                    List<LoCategoryInfo> loCategories = loService.getLoCategoriesForLo(loId);
046                                    loDisplay.setLoCategoryInfoList(loCategories);
047                            } catch (DoesNotExistException e) {
048                            } catch (Exception e) {
049                                    throw new AssemblyException("Error getting learning objective categories", e);
050                            }
051                            try {
052                                    List<LoInfo> childLos = loService.getRelatedLosByLoId(loId,CourseAssemblerConstants.COURSE_LO_RELATION_INCLUDES);
053                                    for(LoInfo childLo:childLos){
054                                            LoDisplayInfo childLoDisplay = assemble(childLo, null, shallowBuild);
055                                            childLoDisplay.setParentLoRelationid(lo.getId());
056                                            childLoDisplay.setParentRelType(CourseAssemblerConstants.COURSE_LO_RELATION_INCLUDES);
057                                            loDisplay.getLoDisplayInfoList().add(childLoDisplay);
058                                    }
059                                    if(loDisplay.getLoDisplayInfoList().size()>1){
060                                            Collections.sort(loDisplay.getLoDisplayInfoList(), LoDisplayComparator.getInstance());
061                                    }
062                            } catch (DoesNotExistException e) {
063                            } catch (Exception e) {
064                                    throw new AssemblyException("Error getting learning objective", e);
065                            }
066    
067                    }
068                    return loDisplay;
069            }
070    
071            @Override
072            //Creation of categories is done in the LoCategoryRpcGwtServlet
073            public BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> disassemble(
074                            LoDisplayInfo loDisplay, NodeOperation operation)
075                            throws AssemblyException {
076                    
077                    BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> result = new BaseDTOAssemblyNode<LoDisplayInfo, LoInfo>(this);
078                    
079                    //see if this is a new LuInfo
080                    if (loDisplay == null || loDisplay.getLoInfo() == null) {
081                            throw new AssemblyException("Lo can not be null");
082                    }
083                    if (NodeOperation.CREATE != operation && null == loDisplay.getLoInfo().getId()) {
084                            throw new AssemblyException("Lo id can not be null");
085                    }
086                    
087                    //set the id if it's not there already(important for creating relations)
088                    loDisplay.getLoInfo().setId(UUIDHelper.genStringUUID(loDisplay.getLoInfo().getId()));
089                    
090                    //Default these values
091                    loDisplay.getLoInfo().setType(CourseAssemblerConstants.COURSE_LO_TYPE);
092                    loDisplay.getLoInfo().setLoRepositoryKey(CourseAssemblerConstants.COURSE_LO_REPOSITORY_KEY);
093                    
094                    
095                    //Populate the node
096                    result.setBusinessDTORef(loDisplay);
097                    result.setNodeData(loDisplay.getLoInfo());
098                    result.setOperation(operation);
099                    
100                    //Process the child los
101                    try {
102                            List<BaseDTOAssemblyNode<?, ?>> childLoNodes = disassembleChildLos(loDisplay, operation);
103                            result.getChildNodes().addAll(childLoNodes);
104                    } catch (DoesNotExistException e) {
105                    } catch (Exception e) {
106                            throw new AssemblyException("Error disassembling child los", e);
107                    } 
108    
109                    //Process the categories
110                    try {
111                            List<BaseDTOAssemblyNode<?, ?>> categoryNodes = disassembleCategories(loDisplay, operation);
112                            result.getChildNodes().addAll(categoryNodes);
113                    } catch (Exception e) {
114                            throw new AssemblyException("Error disassembling categories", e);
115                    } 
116                    
117                    return result;
118            }
119    
120            private List<BaseDTOAssemblyNode<?, ?>> disassembleCategories(
121                            LoDisplayInfo loDisplay, NodeOperation operation) throws AssemblyException {
122                    
123                    List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
124                    
125                    //Category relations
126                    Set<String> currentCategoryIds = new HashSet<String>();
127                    //Get current relations
128                    if (!NodeOperation.CREATE.equals(operation)) {
129                            try {
130                                    List<LoCategoryInfo> categories = loService.getLoCategoriesForLo(loDisplay.getLoInfo().getId());
131                                    for (LoCategoryInfo category : categories) {
132                                            currentCategoryIds.add(category.getId());
133                                    }
134                            } catch (DoesNotExistException e) {
135                            } catch (Exception e) {
136                                    throw new AssemblyException("Error getting categories", e);
137                            }
138                    }
139                    //Update
140                    for (LoCategoryInfo category : loDisplay.getLoCategoryInfoList()) {
141    
142                            // If this is a format create/new activity update then all activities will be created
143                        if (NodeOperation.CREATE == operation
144                                || (NodeOperation.UPDATE == operation &&  !currentCategoryIds.contains(category.getId()))) {
145                            
146                            LoCategoryRelationInfo loCategoryRelation = new LoCategoryRelationInfo();
147                            loCategoryRelation.setCategoryId(category.getId());
148                            loCategoryRelation.setLoId(loDisplay.getLoInfo().getId());
149                            
150                    BaseDTOAssemblyNode<LoDisplayInfo, LoCategoryRelationInfo> relationToAddNode = new BaseDTOAssemblyNode<LoDisplayInfo, LoCategoryRelationInfo>(null);
151                    relationToAddNode.setNodeData(loCategoryRelation);
152                    relationToAddNode.setOperation(NodeOperation.CREATE);
153                    results.add(relationToAddNode);
154    
155                    currentCategoryIds.remove(category.getId());
156                        } else if (NodeOperation.UPDATE == operation
157                                            && currentCategoryIds.contains(category.getId())) {
158                    currentCategoryIds.remove(category.getId());
159                            } 
160                    }
161                    //Delete leftovers
162                    for(String categoryId:currentCategoryIds){
163                    LoCategoryRelationInfo loCategoryRelation = new LoCategoryRelationInfo();
164                    loCategoryRelation.setCategoryId(categoryId);
165                    loCategoryRelation.setLoId(loDisplay.getLoInfo().getId());
166                    
167                BaseDTOAssemblyNode<LoDisplayInfo, LoCategoryRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<LoDisplayInfo, LoCategoryRelationInfo>(null);
168                relationToDeleteNode.setNodeData(loCategoryRelation);
169                relationToDeleteNode.setOperation(NodeOperation.DELETE);
170                results.add(relationToDeleteNode);
171                    }
172                    
173                    return results;
174            }
175    
176            private List<BaseDTOAssemblyNode<?, ?>> disassembleChildLos(LoDisplayInfo loDisplay, NodeOperation operation) throws DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException, AssemblyException{
177                    List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
178                    Map<String,LoLoRelationInfo> currentLoRelations = new HashMap<String,LoLoRelationInfo>();
179                    //Make lu lu relations
180                    if (!NodeOperation.CREATE.equals(operation)) {
181                            try {
182                                    List<LoLoRelationInfo> loRelations = loService.getLoLoRelationsByLoId(loDisplay.getLoInfo().getId());
183                                    for (LoLoRelationInfo loRelation : loRelations) {
184                                            //getLoLoRelationsByLoId returns if the lo is related or if it is the owner(this seems wrong)
185                                            if(CourseAssemblerConstants.COURSE_LO_RELATION_INCLUDES.equals(loRelation.getType())&&
186                                                            !loDisplay.getLoInfo().getId().equals(loRelation.getRelatedLoId())){
187                                                    currentLoRelations.put(loRelation.getRelatedLoId(), loRelation);
188                                            }
189                                    }
190                            } catch (DoesNotExistException e) {
191                            } catch (Exception e) {
192                                    throw new AssemblyException("Error getting categories", e);
193                            }
194                    }
195                    
196                    // Loop through all the activities in this format
197                    for (LoDisplayInfo childDisplay : loDisplay.getLoDisplayInfoList()) {
198                        
199                        // Set the state of the child LO to match the state of the parent
200                        // LO. This end up propagating the program state to all of the LOs,
201                        // since we set parent LO state to program state earlier in the code
202                        childDisplay.getLoInfo().setState(loDisplay.getLoInfo().getState());
203                        
204                            // If this is a format create/new activity update then all activities will be created
205                        if (NodeOperation.CREATE == operation
206                                || (NodeOperation.UPDATE == operation &&  !currentLoRelations.containsKey(childDisplay.getLoInfo().getId()))) {
207                                                
208                    // the lo does not exist, so create
209                    // Assemble and add the lo
210                            childDisplay.getLoInfo().setId(null);
211                    BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = this
212                            .disassemble(childDisplay, NodeOperation.CREATE);
213                    results.add(loNode);
214    
215                    // Create the relationship and add it as well
216                    LoLoRelationInfo relation = new LoLoRelationInfo();
217                    relation.setLoId(loDisplay.getLoInfo().getId());
218                    relation.setRelatedLoId(loNode.getNodeData().getId());
219                    relation.setType(CourseAssemblerConstants.COURSE_LO_RELATION_INCLUDES);
220                    
221                    // Relations can only have states of Active or SUSPENDED
222                    // DO NOT use states like Approve, Draft, etc on relations
223                    // Will default to Active
224                    relation.setState(DtoConstants.STATE_ACTIVE);
225                    
226    
227                    BaseDTOAssemblyNode<LoDisplayInfo, LoLoRelationInfo> relationNode = new BaseDTOAssemblyNode<LoDisplayInfo, LoLoRelationInfo>(
228                            null);
229                    relationNode.setNodeData(relation);
230                    relationNode.setOperation(NodeOperation.CREATE);
231    
232                    results.add(relationNode);
233                } else if (NodeOperation.UPDATE == operation
234                                            && currentLoRelations.containsKey(childDisplay.getLoInfo().getId())) {
235                                    // If the lo already has this child lo, then just update the
236                                    // child lo
237                                    BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = this
238                                                    .disassemble(childDisplay, NodeOperation.UPDATE);
239                                    results.add(loNode);
240    
241                                    // remove this entry from the map so we can tell what needs to
242                                    // be deleted at the end
243                                    currentLoRelations.remove(childDisplay.getLoInfo().getId());
244                            } else if (NodeOperation.DELETE == operation
245                        && currentLoRelations.containsKey(childDisplay.getLoInfo().getId())) {
246                                
247                    // Delete the Format and its relation
248                                    LoLoRelationInfo relationToDelete = currentLoRelations.get(childDisplay.getLoInfo().getId());
249                    BaseDTOAssemblyNode<LoDisplayInfo, LoLoRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<LoDisplayInfo, LoLoRelationInfo>(
250                            null);
251                    relationToDeleteNode.setNodeData(relationToDelete);
252                    relationToDeleteNode.setOperation(NodeOperation.DELETE);
253                    results.add(relationToDeleteNode);
254                
255                    BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = this
256                    .disassemble(childDisplay, NodeOperation.DELETE);
257                    results.add(loNode);                                
258    
259                    // remove this entry from the map so we can tell what needs to
260                    // be deleted at the end
261                    currentLoRelations.remove(childDisplay.getLoInfo().getId());                        
262                            }
263                    }         
264    
265            // Now any leftover lo ids are no longer needed, so delete
266            // los and relations
267            for (Entry<String, LoLoRelationInfo> entry : currentLoRelations.entrySet()) {
268                // Create a new relation with the id of the relation we want to
269                // delete
270                    LoLoRelationInfo relationToDelete = entry.getValue();
271                BaseDTOAssemblyNode<LoDisplayInfo, LoLoRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<LoDisplayInfo, LoLoRelationInfo>(
272                        null);
273                relationToDeleteNode.setNodeData(relationToDelete);
274                relationToDeleteNode.setOperation(NodeOperation.DELETE);
275                results.add(relationToDeleteNode);
276    
277                LoInfo loToDelete = loService.getLo(entry.getKey());
278                LoDisplayInfo loDisplayToDelete = this.assemble(loToDelete, null, false);
279                BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = this.disassemble(loDisplayToDelete, NodeOperation.DELETE);
280                results.add(loNode);                                            
281            }
282                    return results;
283                    
284            }
285    
286            public void setLoService(LearningObjectiveService loService) {
287                    this.loService = loService;
288            }
289    
290            public static class LoDisplayComparator implements Comparator<LoDisplayInfo>{
291                    private static LoDisplayComparator instance = new LoDisplayComparator();
292                    @Override
293                    public int compare(LoDisplayInfo o1, LoDisplayInfo o2) {
294                            String o1Sequence = o1.getLoInfo().getAttributes().get(CourseAssemblerConstants.COURSE_LO_SEQUENCE);
295                            String o2Sequence = o1.getLoInfo().getAttributes().get(CourseAssemblerConstants.COURSE_LO_SEQUENCE);
296                            if(o1Sequence!=null){
297                                    return o1Sequence.compareTo(o2Sequence);
298                            }else if(o2Sequence!=null){
299                                    return -1;
300                            }
301                            return 0;
302                    }
303                    public static LoDisplayComparator getInstance() {
304                            return instance;
305                    }
306            }
307    }