001    /*
002     * Copyright 2008 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 1.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * 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.lum.course.service.assembler;
017    
018    
019    
020    import org.apache.log4j.Logger;
021    import org.kuali.student.common.util.UUIDHelper;
022    import org.kuali.student.r1.common.assembly.BOAssembler;
023    import org.kuali.student.r1.common.assembly.BaseDTOAssemblyNode;
024    import org.kuali.student.r1.common.assembly.BaseDTOAssemblyNode.NodeOperation;
025    import org.kuali.student.r2.common.assembler.AssemblyException;
026    import org.kuali.student.r2.common.dto.AttributeInfo;
027    import org.kuali.student.r2.common.dto.ContextInfo;
028    import org.kuali.student.r2.common.dto.DtoConstants;
029    import org.kuali.student.r2.common.dto.RichTextInfo;
030    import org.kuali.student.r2.common.exceptions.DoesNotExistException;
031    import org.kuali.student.r2.common.exceptions.InvalidParameterException;
032    import org.kuali.student.r2.common.exceptions.MissingParameterException;
033    import org.kuali.student.r2.common.exceptions.OperationFailedException;
034    import org.kuali.student.r2.common.exceptions.PermissionDeniedException;
035    import org.kuali.student.r2.core.atp.dto.AtpInfo;
036    import org.kuali.student.r2.core.atp.service.AtpService;
037    import org.kuali.student.r2.lum.clu.dto.AdminOrgInfo;
038    import org.kuali.student.r2.lum.clu.dto.CluAccountingInfo;
039    import org.kuali.student.r2.lum.clu.dto.CluCluRelationInfo;
040    import org.kuali.student.r2.lum.clu.dto.CluFeeInfo;
041    import org.kuali.student.r2.lum.clu.dto.CluFeeRecordInfo;
042    import org.kuali.student.r2.lum.clu.dto.CluIdentifierInfo;
043    import org.kuali.student.r2.lum.clu.dto.CluInfo;
044    import org.kuali.student.r2.lum.clu.dto.CluLoRelationInfo;
045    import org.kuali.student.r2.lum.clu.dto.CluResultInfo;
046    import org.kuali.student.r2.lum.clu.dto.LuCodeInfo;
047    import org.kuali.student.r2.lum.clu.dto.ResultOptionInfo;
048    import org.kuali.student.r2.lum.clu.service.CluService;
049    import org.kuali.student.r2.lum.course.dto.CourseCrossListingInfo;
050    import org.kuali.student.r2.lum.course.dto.CourseExpenditureInfo;
051    import org.kuali.student.r2.lum.course.dto.CourseFeeInfo;
052    import org.kuali.student.r2.lum.course.dto.CourseInfo;
053    import org.kuali.student.r2.lum.course.dto.CourseJointInfo;
054    import org.kuali.student.r2.lum.course.dto.CourseRevenueInfo;
055    import org.kuali.student.r2.lum.course.dto.CourseVariationInfo;
056    import org.kuali.student.r2.lum.course.dto.FormatInfo;
057    import org.kuali.student.r2.lum.course.dto.LoDisplayInfo;
058    import org.kuali.student.r2.lum.lo.dto.LoInfo;
059    import org.kuali.student.r2.lum.lo.service.LearningObjectiveService;
060    import org.kuali.student.r2.lum.lrc.dto.ResultValueRangeInfo;
061    import org.kuali.student.r2.lum.lrc.dto.ResultValuesGroupInfo;
062    import org.kuali.student.r2.lum.lrc.service.LRCService;
063    import org.kuali.student.r2.lum.service.assembler.CluAssemblerUtils;
064    import org.kuali.student.r2.lum.util.constants.LrcServiceConstants;
065    import org.springframework.util.StringUtils;
066    
067    import java.util.ArrayList;
068    import java.util.Collections;
069    import java.util.Date;
070    import java.util.HashMap;
071    import java.util.HashSet;
072    import java.util.Iterator;
073    import java.util.List;
074    import java.util.Map;
075    import java.util.Map.Entry;
076    import java.util.Set;
077    
078    /**
079     * Assembler for CourseInfo. Provides assemble and disassemble operation on
080     * CourseInfo from/to CluInfo and other base DTOs
081     *
082     * @author Kuali Student Team
083     *
084     */
085    
086    public class CourseAssembler implements BOAssembler<CourseInfo, CluInfo> {
087    
088        final static Logger LOG = Logger.getLogger(CourseAssembler.class);
089        private CluService cluService;
090        private FormatAssembler formatAssembler;
091        private CourseJointAssembler courseJointAssembler;
092        private LoAssembler loAssembler;
093        private LearningObjectiveService loService;
094        private CluAssemblerUtils cluAssemblerUtils;
095        private LRCService lrcService;
096        private AtpService atpService;
097        private float defaultCreditIncrement = 1.0f;
098        //
099        @Override
100        public CourseInfo assemble(CluInfo clu, CourseInfo courseInfo,
101                                   boolean shallowBuild,ContextInfo contextInfo) throws AssemblyException {
102    
103            CourseInfo course = (null != courseInfo) ? courseInfo
104                    : new CourseInfo();
105    
106            // Copy all the data from the clu to the course
107    
108            course.setAttributes(clu.getAttributes());
109            course.setCampusLocations(clu.getCampusLocations());
110            course.setCode(clu.getOfficialIdentifier().getCode());
111            course.setCourseNumberSuffix(clu.getOfficialIdentifier()
112                    .getSuffixCode());
113            course.setLevel(clu.getOfficialIdentifier().getLevel());
114            course.setOutOfClassHours(clu.getIntensity());
115            course.setInstructors(clu.getInstructors());
116            course.setStartTerm(clu.getExpectedFirstAtp());
117            course.setEndTerm(clu.getLastAtp());
118            course.setCourseTitle(clu.getOfficialIdentifier().getLongName());
119    
120            // CrossListings
121            List<CourseCrossListingInfo> crossListings = assembleCrossListings(clu.getAlternateIdentifiers());
122            course.setCrossListings(crossListings);
123    
124            //Variation
125            List<CourseVariationInfo> variations = assembleVariations(clu.getAlternateIdentifiers());
126            course.setVariations(variations);
127    
128    //        course.setDepartment(clu.getPrimaryAdminOrg().getOrgId());
129            if(course.getUnitsDeployment()==null){
130                course.setUnitsDeployment(new ArrayList<String>());
131            }
132            if(course.getUnitsContentOwner()==null){
133                course.setUnitsContentOwner(new ArrayList<String>());
134            }
135            List<String> courseAdminOrgs = new ArrayList<String>();
136            List<String> courseSubjectOrgs = new ArrayList<String>();
137            for(AdminOrgInfo adminOrg: clu.getAdminOrgs()){
138                if(adminOrg.getTypeKey().equals(CourseAssemblerConstants.ADMIN_ORG)){
139                    courseAdminOrgs.add(adminOrg.getOrgId());
140                }
141                if(adminOrg.getTypeKey().equals(CourseAssemblerConstants.SUBJECT_ORG)){
142                    courseSubjectOrgs.add(adminOrg.getOrgId());
143                }
144            }
145            course.setUnitsDeployment(courseAdminOrgs);
146            course.setUnitsContentOwner(courseSubjectOrgs);
147            course.setDescr(clu.getDescr());
148            course.setDuration(clu.getStdDuration());
149            course.setEffectiveDate(clu.getEffectiveDate());
150            course.setExpirationDate(clu.getExpirationDate());
151    
152            //Fees
153            //Fee justification
154            List<CourseFeeInfo> fees = new ArrayList<CourseFeeInfo>();
155            List<CourseRevenueInfo> revenues = new ArrayList<CourseRevenueInfo>();
156            if(clu.getFeeInfo() != null){
157                course.setFeeJustification(clu.getFeeInfo().getDescr());
158    
159                //Fees and revenues come from the same place but revenues have a special feeType
160                for(CluFeeRecordInfo cluFeeRecord: clu.getFeeInfo().getCluFeeRecords()){
161                    String feeType = cluFeeRecord.getFeeType();
162                    if(CourseAssemblerConstants.COURSE_FINANCIALS_REVENUE_TYPE.equals(feeType)){
163                        CourseRevenueInfo courseRevenue = new CourseRevenueInfo();
164                        courseRevenue.setFeeType(feeType);
165                        courseRevenue.setAffiliatedOrgs(cluFeeRecord.getAffiliatedOrgs());
166                        courseRevenue.setAttributes(cluFeeRecord.getAttributes());
167                        courseRevenue.setId(cluFeeRecord.getId());
168                        courseRevenue.setMeta(cluFeeRecord.getMeta());
169                        revenues.add(courseRevenue);
170                    }else{
171                        CourseFeeInfo courseFee = new CourseFeeInfo();
172                        courseFee.setFeeType(feeType);
173                        courseFee.setRateType(cluFeeRecord.getRateType());
174                        courseFee.setDescr(cluFeeRecord.getDescr());
175                        courseFee.setMeta(cluFeeRecord.getMeta());
176                        courseFee.setId(cluFeeRecord.getId());
177                        courseFee.setFeeAmounts(cluFeeRecord.getFeeAmounts());
178                        courseFee.setAttributes(cluFeeRecord.getAttributes());
179                        fees.add(courseFee);
180                    }
181                }
182            }
183            course.setFees(fees);
184            course.setRevenues(revenues);
185            //Expenditures are mapped from accounting info
186            if(course.getExpenditure() == null || clu.getAccountingInfo() == null){
187                course.setExpenditure(new CourseExpenditureInfo());
188            }
189            if(clu.getAccountingInfo() != null){
190                course.getExpenditure().setAffiliatedOrgs(clu.getAccountingInfo().getAffiliatedOrgs());
191            }
192    
193            course.setId(clu.getId());
194            course.setTypeKey(clu.getTypeKey());
195            course.setTermsOffered(clu.getOfferedAtpTypes());
196            course.setPrimaryInstructor(clu.getPrimaryInstructor());
197            course.setInstructors(clu.getInstructors());
198            course.setStateKey(clu.getStateKey());
199            course.setSubjectArea(clu.getOfficialIdentifier().getDivision());
200            course.setTranscriptTitle(clu.getOfficialIdentifier().getShortName());
201            course.setMeta(clu.getMeta());
202            course.setVersion(clu.getVersion());
203    
204    
205            //Special topics code
206            course.setSpecialTopicsCourse(false);
207            for(LuCodeInfo luCode : clu.getLuCodes()){
208                if(CourseAssemblerConstants.COURSE_CODE_SPECIAL_TOPICS.equals(luCode.getType())){
209                    course.setSpecialTopicsCourse(Boolean.parseBoolean(luCode.getValue()));
210                    break;
211                }
212            }
213            //Pilot Course code
214            course.setPilotCourse(false);
215            for(LuCodeInfo luCode : clu.getLuCodes()){
216                if(CourseAssemblerConstants.COURSE_CODE_PILOT_COURSE.equals(luCode.getType())){
217                    course.setPilotCourse(Boolean.parseBoolean(luCode.getValue()));
218                    break;
219                }
220            }
221    
222            // Don't make any changes to nested datastructures if this is
223            if (!shallowBuild) {
224                try {
225                    // Use the cluService to find Joints, then convert and add to the
226                    // course
227                    List<CluCluRelationInfo> cluClus = cluService.getCluCluRelationsByClu(clu.getId(),contextInfo);
228    
229                    for (CluCluRelationInfo cluRel : cluClus) {
230                        if (cluRel.getTypeKey().equals(CourseAssemblerConstants.JOINT_RELATION_TYPE)) {
231                            CourseJointInfo jointInfo = null;
232                            if(cluRel.getCluId().equals(clu.getId())){
233                                jointInfo = courseJointAssembler.assemble(cluRel, cluRel.getRelatedCluId(), null, false, contextInfo);
234                            }else{
235                                jointInfo = courseJointAssembler.assemble(cluRel, cluRel.getCluId(), null, false, contextInfo);
236                            }
237                            if (jointInfo != null){
238                                course.getJoints().add(jointInfo);
239                            }
240                        }
241                    }
242                } catch (DoesNotExistException e) {
243                } catch (Exception e) {
244                    throw new AssemblyException("Error getting course joints", e);
245                }
246    
247                try {
248                    // Use the cluService to find formats, then convert and add to
249                    // the course
250                    List<CluInfo> formats = cluService.getRelatedClusByCluAndRelationType(course
251                            .getId(),
252                            CourseAssemblerConstants.COURSE_FORMAT_RELATION_TYPE , new ContextInfo());
253    
254                    for (CluInfo format : formats) {
255                        FormatInfo formatInfo = formatAssembler.assemble(format,
256                                null, false,contextInfo);
257                        course.getFormats().add(formatInfo);
258                    }
259    
260                } catch (DoesNotExistException e) {
261                } catch (Exception e) {
262                    throw new AssemblyException("Error getting related formats", e);
263                }
264    
265                try{
266                    //Set Credit and Grading options
267                    List<CluResultInfo> cluResults = cluService.getCluResultByClu(course.getId(),contextInfo);
268    
269                    List<ResultValuesGroupInfo> creditOptions = assembleCreditOptions(cluResults, contextInfo);
270    
271                    course.setCreditOptions(creditOptions);
272    
273                    List<String> gradingOptions = assembleGradingOptions(cluResults);
274    
275                    course.setGradingOptions(gradingOptions);
276                } catch (DoesNotExistException e){
277                } catch (Exception e) {
278                    throw new AssemblyException("Error getting course results", e);
279                }
280    
281                //Learning Objectives
282                course.getCourseSpecificLOs().addAll(cluAssemblerUtils.assembleLos(course.getId(), shallowBuild, contextInfo));
283    
284            }
285    
286            //Remove special cases for grading options
287            boolean isAudit = course.getGradingOptions().remove(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_AUDIT);
288            //course.setAttributeValue(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_AUDIT, Boolean.toString(isAudit));
289            return course;
290        }
291    
292        @Override
293        public BaseDTOAssemblyNode<CourseInfo, CluInfo> disassemble(
294                CourseInfo course, NodeOperation operation,ContextInfo contextInfo)
295                throws AssemblyException, PermissionDeniedException {
296    
297            if (course == null) {
298                // FIXME Unsure now if this is an exception or just return null or
299                // empty assemblyNode
300                LOG.error("Course to disassemble is null!");
301                throw new AssemblyException("Course can not be null");
302            }
303    
304            BaseDTOAssemblyNode<CourseInfo, CluInfo> result = new BaseDTOAssemblyNode<CourseInfo, CluInfo>(
305                    this);
306    
307            CluInfo clu;
308            try {
309                clu = (NodeOperation.UPDATE == operation) ? cluService.getClu(course.getId(),contextInfo) : new CluInfo();
310            } catch (Exception e) {
311                throw new AssemblyException("Error getting existing learning unit during course update", e);
312            }
313    
314            // Create the id if it's not there already(important for creating
315            // relations)
316            clu.setId(UUIDHelper.genStringUUID(course.getId()));
317            if (null == course.getId()) {
318                course.setId(clu.getId());
319            }
320            clu.setTypeKey(CourseAssemblerConstants.COURSE_TYPE);
321            clu.setStateKey(course.getStateKey());
322            clu.setIsEnrollable(false);
323    
324            CluIdentifierInfo identifier = new CluIdentifierInfo();
325            identifier.setTypeKey(CourseAssemblerConstants.COURSE_OFFICIAL_IDENT_TYPE);
326            identifier.setStateKey(course.getStateKey());
327            identifier.setLongName(course.getCourseTitle());
328            identifier.setShortName(course.getTranscriptTitle());
329            identifier.setSuffixCode(course.getCourseNumberSuffix());
330            identifier.setDivision(course.getSubjectArea());
331            identifier.setCode(course.getCode());
332    
333            //Custom logic to set the level, if level not provided
334            if(StringUtils.hasText(course.getLevel())) {
335                identifier.setLevel(course.getLevel());
336            } else if(course.getCourseNumberSuffix()!=null&&course.getCourseNumberSuffix().length()>=3){
337                identifier.setLevel(course.getCourseNumberSuffix().substring(0, 1)+"00");
338            }
339    
340            clu.setOfficialIdentifier(identifier);
341    
342            clu.setAdminOrgs(new ArrayList<AdminOrgInfo>());
343    
344            // Use the Course Variation assembler to disassemble the variations
345    
346            // copy all fields
347            //Remove any existing variations or crosslistings
348            for(Iterator<CluIdentifierInfo> iter = clu.getAlternateIdentifiers().iterator();iter.hasNext();){
349                CluIdentifierInfo cluIdentifier = iter.next();
350                if(CourseAssemblerConstants.COURSE_VARIATION_IDENT_TYPE.equals(cluIdentifier.getTypeKey()) ||
351                        CourseAssemblerConstants.COURSE_CROSSLISTING_IDENT_TYPE.equals(cluIdentifier.getTypeKey()) ){
352                    iter.remove();
353                }
354            }
355            //Add in variations
356            for(CourseVariationInfo variation:course.getVariations()){
357                CluIdentifierInfo cluIdentifier = new CluIdentifierInfo();
358                cluIdentifier.setId(variation.getId());
359                cluIdentifier.setTypeKey(CourseAssemblerConstants.COURSE_VARIATION_IDENT_TYPE);
360                cluIdentifier.setCode(identifier.getCode());
361                cluIdentifier.setSuffixCode(course.getCourseNumberSuffix());
362                cluIdentifier.setDivision(course.getSubjectArea());
363                cluIdentifier.setVariation(variation.getVariationCode());
364                cluIdentifier.setLongName(variation.getVariationTitle());
365                cluIdentifier.setStateKey(course.getStateKey());
366                clu.getAlternateIdentifiers().add(cluIdentifier);
367            }
368            //Add in crosslistings
369            for(CourseCrossListingInfo crossListing:course.getCrossListings()){
370                CluIdentifierInfo cluIdentifier = new CluIdentifierInfo();
371                cluIdentifier.setId(crossListing.getId());
372                cluIdentifier.setTypeKey(CourseAssemblerConstants.COURSE_CROSSLISTING_IDENT_TYPE);
373                cluIdentifier.setSuffixCode(crossListing.getCourseNumberSuffix());
374                cluIdentifier.setDivision(crossListing.getSubjectArea());
375                cluIdentifier.setStateKey(course.getStateKey());
376                cluIdentifier.setOrgId(crossListing.getDepartment());
377                cluIdentifier.setAttributes(crossListing.getAttributes());
378                cluIdentifier.setCode(crossListing.getCode());
379                clu.getAlternateIdentifiers().add(cluIdentifier);
380            }
381    
382            List<AdminOrgInfo> adminOrgInfos = new ArrayList<AdminOrgInfo>();
383            for(String org:course.getUnitsDeployment()){
384                AdminOrgInfo adminOrg = new AdminOrgInfo();
385                adminOrg.setTypeKey(CourseAssemblerConstants.ADMIN_ORG);
386                adminOrg.setOrgId(org);
387                adminOrgInfos.add(adminOrg);
388            }
389            clu.getAdminOrgs().addAll(adminOrgInfos);
390    
391            List<AdminOrgInfo> subjectOrgs = new ArrayList<AdminOrgInfo>();
392            for (String subOrg : course.getUnitsContentOwner()) {
393                AdminOrgInfo subjectOrg = new AdminOrgInfo();
394                subjectOrg.setTypeKey(CourseAssemblerConstants.SUBJECT_ORG);
395                subjectOrg.setOrgId(subOrg);
396                subjectOrgs.add(subjectOrg);
397            }
398            clu.getAdminOrgs().addAll(subjectOrgs);
399    
400    
401            clu.setAttributes(course.getAttributes());
402            clu.setCampusLocations(course.getCampusLocations());
403            clu.setDescr(course.getDescr());
404            clu.setStdDuration(course.getDuration());
405    
406            //Default course effective dates to the atps if entered
407            if(course.getStartTerm() != null){
408                try {
409                    AtpInfo startAtp = atpService.getAtp(course.getStartTerm(), contextInfo);
410                    course.setEffectiveDate(startAtp.getStartDate());
411                } catch (Exception e) {
412                    throw new AssemblyException("Error getting start term Atp.",e);
413                }
414            }
415            if(course.getEndTerm() != null){
416                try {
417                    AtpInfo endAtp = atpService.getAtp(course.getEndTerm(), contextInfo);
418                    course.setExpirationDate(endAtp.getEndDate());
419                } catch (Exception e) {
420                    throw new AssemblyException("Error getting end term Atp.",e);
421                }
422            }
423    
424            clu.setEffectiveDate(course.getEffectiveDate());
425            clu.setExpirationDate(course.getExpirationDate());
426    
427            clu.setOfferedAtpTypes(course.getTermsOffered());
428            clu.setPrimaryInstructor(course.getPrimaryInstructor());
429    
430            clu.setIntensity(course.getOutOfClassHours());
431            clu.setInstructors(course.getInstructors());
432    
433            clu.setExpectedFirstAtp(course.getStartTerm());
434            clu.setLastAtp(course.getEndTerm());
435    
436            clu.setMeta(course.getMeta());
437            clu.setVersion(course.getVersion());
438    
439            // Add the Clu to the result
440            result.setNodeData(clu);
441            result.setOperation(operation);
442            result.setBusinessDTORef(course);
443    
444            // Use the Format assembler to disassemble the formats and relations
445            List<BaseDTOAssemblyNode<?, ?>> formatResults;
446            try {
447                formatResults = disassembleFormats(clu.getId(), course, operation,contextInfo);
448                result.getChildNodes().addAll(formatResults);
449    
450            } catch (DoesNotExistException e) {
451            } catch (Exception e) {
452                throw new AssemblyException("Error while disassembling format", e);
453            }
454    
455            // Use the CourseJoint assembler to disassemble the CourseJoints and
456            // relations
457            List<BaseDTOAssemblyNode<?, ?>> courseJointResults = disassembleJoints(clu.getId(), course, operation,contextInfo);
458            result.getChildNodes().addAll(courseJointResults);
459    
460            //Disassemble the CluResults (grading and credit options)
461            //Special code to take audit from attributes and put into options
462            for (AttributeInfo attr : course.getAttributes()){
463                if (attr.getKey() != null){
464                    if (attr.getKey().equals(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_AUDIT)
465                            && "true".equals(attr.getValue()) ){
466                        if(!course.getGradingOptions().contains(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_AUDIT)){
467                            course.getGradingOptions().add(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_AUDIT);
468                        }
469                        break;
470                    }
471                }
472            }
473    
474            // Patch to disassemble pass/fail option as well (KSLAB-2633)
475            for (AttributeInfo attr : course.getAttributes()){
476                if (attr.getKey() != null){
477                    if (attr.getKey().equals(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_PASSFAIL)
478                            && "true".equals(attr.getValue()) ){
479                        if(!course.getGradingOptions().contains(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_PASSFAIL)){
480                            course.getGradingOptions().add(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_PASSFAIL);
481                        }
482                        break;
483                    }
484                }
485            }
486            
487          
488            List<CluResultInfo> cluResultList;
489            try {
490                cluResultList = cluService.getCluResultByClu(clu.getId(),contextInfo);
491            } catch (DoesNotExistException e) {
492                cluResultList = Collections.emptyList();
493            } catch (Exception e) {
494                throw new AssemblyException("Error getting cluResults", e);
495            }
496    
497            List<BaseDTOAssemblyNode<?, ?>> creditOutcomes = disassembleCreditOutcomes(course, clu, cluResultList, operation, contextInfo);
498            result.getChildNodes().addAll(creditOutcomes);
499            BaseDTOAssemblyNode<?, ?> gradingOptions = disassembleGradingOptions(clu.getId(), course.getStateKey(), course.getGradingOptions(), cluResultList, operation);
500            result.getChildNodes().add(gradingOptions);
501    
502            //Use the LoAssembler to disassemble Los
503            try {
504                List<BaseDTOAssemblyNode<?, ?>> loResults;
505                loResults = disassembleLos(clu.getId(), course, operation,contextInfo);
506                result.getChildNodes().addAll(loResults);
507            } catch (Exception e) {
508                throw new AssemblyException("Error while disassembling los", e);
509            }
510    
511            //add the special topics code if it did not exist, or remove if it was not wanted
512            boolean alreadyHadSpecialTopicsCode = false;
513            for(Iterator<LuCodeInfo> luCodeIterator = clu.getLuCodes().iterator();luCodeIterator.hasNext();){
514                LuCodeInfo luCode = luCodeIterator.next();
515                if(CourseAssemblerConstants.COURSE_CODE_SPECIAL_TOPICS.equals(luCode.getType())){
516                    alreadyHadSpecialTopicsCode = true;
517                    if(!course.isSpecialTopicsCourse()){
518                        luCodeIterator.remove();
519                    }
520                    break;
521                }
522            }
523            if(!alreadyHadSpecialTopicsCode && course.isSpecialTopicsCourse()){
524                LuCodeInfo luCode = new LuCodeInfo();
525                luCode.setType(CourseAssemblerConstants.COURSE_CODE_SPECIAL_TOPICS);
526                luCode.setValue("true");
527                clu.getLuCodes().add(luCode);
528            }
529    
530            //add the special topics code if it did not exist, or remove if it was not wanted
531            boolean alreadyHadPilotCourseCode = false;
532            for(Iterator<LuCodeInfo> luCodeIterator = clu.getLuCodes().iterator();luCodeIterator.hasNext();){
533                LuCodeInfo luCode = luCodeIterator.next();
534                if(CourseAssemblerConstants.COURSE_CODE_PILOT_COURSE.equals(luCode.getType())){
535                    alreadyHadPilotCourseCode = true;
536                    if(!course.isPilotCourse()){
537                        luCodeIterator.remove();
538                    }
539                    break;
540                }
541            }
542            if(!alreadyHadPilotCourseCode && course.isPilotCourse()){
543                LuCodeInfo luCode = new LuCodeInfo();
544                luCode.setType(CourseAssemblerConstants.COURSE_CODE_PILOT_COURSE);
545                luCode.setValue("true");
546                clu.getLuCodes().add(luCode);
547            }
548    
549            //FEES
550            if(clu.getFeeInfo() == null){
551                clu.setFeeInfo(new CluFeeInfo());
552            }
553            clu.getFeeInfo().setDescr(course.getFeeJustification());
554            clu.getFeeInfo().getCluFeeRecords().clear();
555            for(CourseRevenueInfo courseRevenue:course.getRevenues()){
556                CluFeeRecordInfo cluFeeRecord  = new CluFeeRecordInfo();
557                cluFeeRecord.setFeeType(CourseAssemblerConstants.COURSE_FINANCIALS_REVENUE_TYPE);
558                cluFeeRecord.setRateType(CourseAssemblerConstants.COURSE_FINANCIALS_REVENUE_TYPE);
559    
560                cluFeeRecord.setAttributes(courseRevenue.getAttributes());
561                cluFeeRecord.setAffiliatedOrgs(courseRevenue.getAffiliatedOrgs());
562                cluFeeRecord.setId(courseRevenue.getId());
563                cluFeeRecord.setMeta(courseRevenue.getMeta());
564                clu.getFeeInfo().getCluFeeRecords().add(cluFeeRecord);
565            }
566            for(CourseFeeInfo courseFee : course.getFees()){
567                CluFeeRecordInfo cluFeeRecord  = new CluFeeRecordInfo();
568                cluFeeRecord.setFeeType(courseFee.getFeeType());
569                cluFeeRecord.setRateType(courseFee.getRateType());
570                cluFeeRecord.setDescr(courseFee.getDescr());
571                cluFeeRecord.setMeta(courseFee.getMeta());
572                cluFeeRecord.setId(courseFee.getId());
573                cluFeeRecord.setFeeAmounts(courseFee.getFeeAmounts());
574    
575                cluFeeRecord.setAttributes(courseFee.getAttributes());
576                clu.getFeeInfo().getCluFeeRecords().add(cluFeeRecord);
577            }
578            if(clu.getAccountingInfo() == null || course.getExpenditure()== null){
579                clu.setAccountingInfo( new CluAccountingInfo());
580            }
581            if(course.getExpenditure() != null){
582                clu.getAccountingInfo().setAffiliatedOrgs(course.getExpenditure().getAffiliatedOrgs());
583    
584                clu.getAccountingInfo().setAttributes(course.getExpenditure().getAttributes());
585            }
586    
587            return result;
588        }
589    
590        private List<BaseDTOAssemblyNode<?, ?>> disassembleCreditOutcomes(CourseInfo course, CluInfo clu, List<CluResultInfo> currentCluResults, NodeOperation operation, ContextInfo contextInfo) throws AssemblyException, NumberFormatException {
591    
592            List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
593    
594            String courseResultType = CourseAssemblerConstants.COURSE_RESULT_TYPE_CREDITS;
595    
596            //See if we need to create any new lrcs
597            if(NodeOperation.DELETE!=operation){
598                //Find all the existing LRCs for the following three types
599                Set<String> resultValueGroupIds = new HashSet<String>();
600    
601                try{
602                    try {
603                        resultValueGroupIds.addAll(lrcService.getResultValuesGroupKeysByType(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_FIXED, contextInfo));
604                    } catch (DoesNotExistException e) {}
605                    try {
606                        resultValueGroupIds.addAll(lrcService.getResultValuesGroupKeysByType(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_MULTIPLE, contextInfo));
607                    } catch (DoesNotExistException e) {}
608                    try {
609                        resultValueGroupIds.addAll(lrcService.getResultValuesGroupKeysByType(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_VARIABLE, contextInfo));
610                    } catch (DoesNotExistException e) {}
611    
612                    //Create any LRCs that do not yet exist
613                    for(ResultValuesGroupInfo creditOption:course.getCreditOptions()){
614    
615                        String id = null;
616                        String type = null;
617                        List<String> resultValues = null;
618                        ResultValueRangeInfo resultValueRange = null;
619                        String resultValueKeyPrefix = "kuali.result.value.credit.degree.";
620                        //Depending on the type, set the id, type and result values differently
621                        if(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_FIXED.equals(creditOption.getTypeKey())){
622                            float fixedCreditValue = Float.parseFloat(creditOption.getResultValueRange().getMinValue());
623                            id = CourseAssemblerConstants.COURSE_RESULT_COMP_CREDIT_PREFIX + fixedCreditValue;
624                            type = CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_FIXED;
625                            resultValues = new ArrayList<String>();
626                            resultValues.add(String.valueOf(fixedCreditValue));
627                            resultValueRange = new ResultValueRangeInfo();
628                            resultValueRange.setMinValue(String.valueOf(fixedCreditValue));
629                            resultValueRange.setMaxValue(String.valueOf(fixedCreditValue));
630                        }else if(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_MULTIPLE.equals(creditOption.getTypeKey())){
631                            Collections.sort(creditOption.getResultValueKeys());
632                            StringBuilder sb = new StringBuilder(CourseAssemblerConstants.COURSE_RESULT_COMP_CREDIT_PREFIX);
633                            for(Iterator<String> iter = creditOption.getResultValueKeys().iterator();iter.hasNext();){
634                                String valueKey = iter.next();
635                                if(valueKey.startsWith(resultValueKeyPrefix)){
636                                    valueKey = valueKey.replace(resultValueKeyPrefix,"");
637                                }
638                                float creditValue = Float.parseFloat(valueKey);
639                                sb.append(String.valueOf(creditValue));
640                                if(iter.hasNext()){
641                                    sb.append(",");
642                                }
643                            }
644                            id = sb.toString();
645                            type = CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_MULTIPLE;
646                            resultValues = creditOption.getResultValueKeys();
647                        }else if(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_VARIABLE.equals(creditOption.getTypeKey())){
648                            /*
649                                   * For variable credits create a Result values that goes from min to max with the specified increment.
650                                   * If no increment is specified, use 1.0 as the increment. The increment can be specified as a float.
651                                   */
652    
653                            float minCredits = Float.parseFloat(creditOption.getResultValueRange().getMinValue());
654                            float maxCredits = Float.parseFloat(creditOption.getResultValueRange().getMaxValue());
655                            String creditValueIncr = creditOption.getResultValueRange().getIncrement();
656                            float increment = (null != creditValueIncr && creditValueIncr.length() > 0 ) ? Float.parseFloat(creditValueIncr) : defaultCreditIncrement ;
657    
658                            id = CourseAssemblerConstants.COURSE_RESULT_COMP_CREDIT_PREFIX + String.valueOf(minCredits) + "-" + String.valueOf(maxCredits);
659                            type = CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_VARIABLE;
660                            resultValues = new ArrayList<String>();
661                            for(float i = minCredits; i <= maxCredits; i+=increment){
662                                resultValues.add(String.valueOf(i));
663                            }
664                            resultValueRange = new ResultValueRangeInfo();
665                            resultValueRange.setMinValue(String.valueOf(minCredits));
666                            resultValueRange.setMaxValue(String.valueOf(maxCredits));
667                            resultValueRange.setIncrement(String.valueOf(increment));
668                        }
669    
670                        //Set the id
671                        creditOption.setKey(id);
672    
673                        //Ensure the resultValueKey has the proper prefix
674                        for(int i = 0; i < resultValues.size(); i++){
675                            if (!resultValues.get(i).contains("kuali.result.value")){ //only add the prefix if this is not a proper key
676                                resultValues.set(i,resultValueKeyPrefix+Float.parseFloat(resultValues.get(i)));
677                            }
678                        }
679    
680                        //Create a new result component
681                        if(id != null && !resultValueGroupIds.contains(id)){
682    
683                            //Build the new ResultValuesGroup
684                            ResultValuesGroupInfo resultValueGroup = new ResultValuesGroupInfo();
685                            resultValueGroup.setKey(id);
686                            resultValueGroup.setTypeKey(type);
687                            if (DtoConstants.STATE_DRAFT.equals(course.getStateKey())){
688                                resultValueGroup.setStateKey(LrcServiceConstants.RESULT_GROUPS_STATE_DRAFT);
689                            } else if (DtoConstants.STATE_APPROVED.equals(course.getStateKey())) {
690                                resultValueGroup.setStateKey(LrcServiceConstants.RESULT_GROUPS_STATE_APPROVED);
691                            } else {
692                                resultValueGroup.setStateKey(LrcServiceConstants.RESULT_GROUPS_STATE_RETIRED);
693                            }
694                            resultValueGroup.setResultScaleKey(LrcServiceConstants.RESULT_SCALE_KEY_CREDIT_DEGREE);
695                            resultValueGroup.setResultValueKeys(resultValues);
696                            resultValueGroup.setResultValueRange(resultValueRange);
697                            BaseDTOAssemblyNode<ResultValuesGroupInfo, ResultValuesGroupInfo> node = new BaseDTOAssemblyNode<ResultValuesGroupInfo, ResultValuesGroupInfo>(null);
698                            node.setOperation(NodeOperation.CREATE);
699                            node.setNodeData(resultValueGroup);
700                            node.setBusinessDTORef(creditOption);
701                            results.add(node);
702    
703                            resultValueGroupIds.add(id);
704                        }
705                    }
706                }catch (NumberFormatException e){
707                    throw new AssemblyException("Invalid Arguments for credit outcome values",e);
708                }catch (Exception e){
709                    throw new AssemblyException("Error Assembling", e);
710                }
711            }
712    
713            //Now do dissassembly for the actual clu-lrc relations and result options
714    
715            // Get the current options and put them in a map of option type id/cluResult
716            Map<String, List<CluResultInfo>> currentResults = new HashMap<String, List<CluResultInfo>>();
717    
718            //If this is not a create, lookup the results for this clu
719            if (!NodeOperation.CREATE.equals(operation)) {
720                for (CluResultInfo currentResult : currentCluResults) {
721                    if (courseResultType.equals(currentResult.getTypeKey())) {
722                        //There should only be one grading option per CluResult for credit outcomes
723                        if(currentResult.getResultOptions().size()==1){
724                            //Create a mapping to a list of cluresults with the same result componentId
725                            String resultComponentId = currentResult.getResultOptions().get(0).getResultComponentId();
726                            if(!currentResults.containsKey(resultComponentId)){
727                                currentResults.put(resultComponentId, new ArrayList<CluResultInfo>());
728                            }
729                            currentResults.get(resultComponentId).add(currentResult);
730                        }else{
731                            LOG.warn("Credit Results should have exactly one result option each");
732                        }
733                    }
734                }
735            }
736    
737            //Loop through options on the course, if they are new, create a new cluResult
738            for(ResultValuesGroupInfo creditOption : course.getCreditOptions()){
739                if (NodeOperation.CREATE == operation
740                        || (NodeOperation.UPDATE == operation && !currentResults.containsKey(creditOption.getKey()) )) {
741    
742                    ResultOptionInfo resultOption = new ResultOptionInfo();
743                    resultOption.setStateKey(course.getStateKey());
744                    resultOption.setResultComponentId(creditOption.getKey());
745    
746                    CluResultInfo cluResult = new CluResultInfo();
747                    cluResult.setCluId(clu.getId());
748                    cluResult.setStateKey(course.getStateKey());
749                    cluResult.setTypeKey(courseResultType);
750    
751                    cluResult.getResultOptions().add(resultOption);
752    
753                    BaseDTOAssemblyNode<ResultValuesGroupInfo, CluResultInfo> cluResultNode = new BaseDTOAssemblyNode<ResultValuesGroupInfo, CluResultInfo>(null);
754                    cluResultNode.setNodeData(cluResult);
755                    cluResultNode.setOperation(NodeOperation.CREATE);
756    
757                    results.add(cluResultNode);
758                } else if (NodeOperation.UPDATE == operation
759                        && currentResults.containsKey(creditOption.getKey())) {
760                    //Get the list from the map and remove an entry, if the list is empty then remove it from the map
761                    List<CluResultInfo> cluResults = currentResults.get(creditOption.getKey());
762                    cluResults.remove(cluResults.size()-1);
763                    if(cluResults.isEmpty()){
764                        currentResults.remove(creditOption.getKey());
765                    }
766                }
767            }
768    
769            //Delete the leftovers
770            for(Entry<String,List<CluResultInfo>> entry:currentResults.entrySet()){
771                for(CluResultInfo cluResult:entry.getValue()){
772                    BaseDTOAssemblyNode<ResultValuesGroupInfo, CluResultInfo> cluResultNode = new BaseDTOAssemblyNode<ResultValuesGroupInfo, CluResultInfo>(null);
773                    cluResultNode.setNodeData(cluResult);
774                    cluResultNode.setOperation(NodeOperation.DELETE);
775                    results.add(cluResultNode);
776                }
777            }
778    
779            return results;
780        }
781    
782        private List<String> assembleGradingOptions(List<CluResultInfo> cluResults){
783    
784            String courseResultType = CourseAssemblerConstants.COURSE_RESULT_TYPE_GRADE;
785    
786            List<String> results = new ArrayList<String>();
787            //Loop through all the CluResults to find the one with the matching type
788            for(CluResultInfo cluResult:cluResults){
789                if(courseResultType.equals(cluResult.getTypeKey())){
790                    //Loop through all options and add to the list of Strings
791                    for(ResultOptionInfo resultOption: cluResult.getResultOptions()){
792                        results.add(resultOption.getResultComponentId());
793                    }
794                    break;
795                }
796            }
797            return results;
798        }
799        //
800        private List<ResultValuesGroupInfo> assembleCreditOptions(
801                List<CluResultInfo> cluResults, ContextInfo contextInfo) throws AssemblyException {
802            String courseResultType = CourseAssemblerConstants.COURSE_RESULT_TYPE_CREDITS;
803            List<ResultValuesGroupInfo> results = new ArrayList<ResultValuesGroupInfo>();
804            //Loop through all the CluResults to find the one with the matching type
805            for(CluResultInfo cluResult:cluResults){
806                if(courseResultType.equals(cluResult.getTypeKey())){
807                    //Loop through all options and add to the list of Strings
808                    for(ResultOptionInfo resultOption: cluResult.getResultOptions()){
809                        try {
810                            if(resultOption.getResultComponentId()!=null){
811                                ResultValuesGroupInfo resultValuesGroup = lrcService.getResultValuesGroup(resultOption.getResultComponentId(), contextInfo);
812                                results.add(resultValuesGroup);
813                            }
814                        } catch (DoesNotExistException e) {
815                            LOG.warn("Course Credit option:"+resultOption.getId()+" refers to non-existant ResultValuesGroupInfo "+ resultOption.getResultComponentId());
816                        } catch (Exception e) {
817                            throw new AssemblyException("Error getting ResultValuesGroupInfo",e);
818                        }
819                    }
820                }
821            }
822            return results;
823        }
824    
825        // TODO Use CluAssemblerUtils
826        private List<BaseDTOAssemblyNode<?, ?>> disassembleLos(String cluId,  CourseInfo course, NodeOperation operation,ContextInfo contextInfo) throws AssemblyException {
827            // TODO Auto-generated method stub
828            List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
829    
830            // Get the current formats and put them in a map of format id/relation
831            // id
832            Map<String, CluLoRelationInfo> currentCluLoRelations = new HashMap<String, CluLoRelationInfo>();
833            try {
834                List<CluLoRelationInfo> cluLoRelations = cluService.getCluLoRelationsByClu(cluId,contextInfo);
835                for(CluLoRelationInfo cluLoRelation:cluLoRelations){
836                    if(CourseAssemblerConstants.COURSE_LO_COURSE_SPECIFIC_RELATION.equals(cluLoRelation.getTypeKey())){
837                        currentCluLoRelations.put(cluLoRelation.getLoId(), cluLoRelation);
838                    }
839                }
840            } catch (DoesNotExistException e) {
841            } catch (Exception e) {
842                throw new AssemblyException("Error finding related Los");
843            }
844    
845            // Loop through all the los in this clu
846            for(LoDisplayInfo loDisplay : course.getCourseSpecificLOs()){
847    
848                // If this is a clu create/new lo update then all los will be created
849                if (NodeOperation.CREATE == operation
850                        || (NodeOperation.UPDATE == operation &&  !currentCluLoRelations.containsKey(loDisplay.getLoInfo().getId()))) {
851    
852                    // the lo does not exist, so create
853                    // Assemble and add the lo
854                    loDisplay.getLoInfo().setId(null);
855                    loDisplay.getLoInfo().setStateKey(course.getStateKey());
856                    BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
857                            .disassemble(loDisplay, NodeOperation.CREATE,contextInfo);
858                    results.add(loNode);
859    
860                    // Create the relationship and add it as well
861                    CluLoRelationInfo relation = new CluLoRelationInfo();
862                    relation.setCluId(cluId);
863                    relation.setLoId(loNode.getNodeData().getId());
864                    relation
865                            .setTypeKey(CourseAssemblerConstants.COURSE_LO_COURSE_SPECIFIC_RELATION);
866                    relation.setStateKey(course.getStateKey());
867    
868                    BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo> relationNode = new BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo>(
869                            null);
870                    relationNode.setNodeData(relation);
871                    relationNode.setOperation(NodeOperation.CREATE);
872    
873                    results.add(relationNode);
874                } else if (NodeOperation.UPDATE == operation
875                        && currentCluLoRelations.containsKey(loDisplay.getLoInfo().getId())) {
876                    loDisplay.getLoInfo().setStateKey(course.getStateKey());
877                    // If the clu already has this lo, then just update the lo
878                    BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
879                            .disassemble(loDisplay, NodeOperation.UPDATE,contextInfo);
880                    results.add(loNode);
881    
882                    // remove this entry from the map so we can tell what needs to
883                    // be deleted at the end
884                    currentCluLoRelations.remove(loDisplay.getLoInfo().getId());
885                } else if (NodeOperation.DELETE == operation
886                        && currentCluLoRelations.containsKey(loDisplay.getLoInfo().getId())) {
887    
888                    // Delete the Format and its relation
889                    CluLoRelationInfo relationToDelete = currentCluLoRelations.get(loDisplay.getLoInfo().getId());
890                    BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo>(
891                            null);
892                    relationToDeleteNode.setNodeData(relationToDelete);
893                    relationToDeleteNode.setOperation(NodeOperation.DELETE);
894                    results.add(relationToDeleteNode);
895    
896                    BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
897                            .disassemble(loDisplay, NodeOperation.DELETE,contextInfo);
898                    results.add(loNode);
899    
900                    // remove this entry from the map so we can tell what needs to
901                    // be deleted at the end
902                    currentCluLoRelations.remove(loDisplay.getLoInfo().getId());
903                }
904            }
905    
906            // Now any leftover lo ids are no longer needed, so delete
907            // los and relations
908            for (Entry<String, CluLoRelationInfo> entry : currentCluLoRelations.entrySet()) {
909                // Create a new relation with the id of the relation we want to
910                // delete
911                CluLoRelationInfo relationToDelete = entry.getValue();
912                BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo>(
913                        null);
914                relationToDeleteNode.setNodeData(relationToDelete);
915                relationToDeleteNode.setOperation(NodeOperation.DELETE);
916                results.add(relationToDeleteNode);
917    
918                try{
919                    LoInfo loToDelete = loService.getLo(entry.getKey(),contextInfo);
920    
921                    LoDisplayInfo loDisplayToDelete = loAssembler.assemble(loToDelete, null, false,contextInfo);
922                    BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
923                            .disassemble(loDisplayToDelete, NodeOperation.DELETE,contextInfo);
924                    results.add(loNode);
925                } catch (DoesNotExistException e){
926                    LOG.warn("Trying to delete non exsistant LO:"+entry.getKey());
927                } catch (Exception e) {
928                    throw new AssemblyException("Error disassembling LOs",e);
929                }
930            }
931    
932            return results;
933        }
934    
935        private BaseDTOAssemblyNode<?, ?> disassembleGradingOptions(String cluId,
936                                                                    String courseState, List<String> options, List<CluResultInfo> currentCluResults, NodeOperation operation) throws AssemblyException {
937            BaseDTOAssemblyNode<List<String>, CluResultInfo> cluResultNode = new BaseDTOAssemblyNode<List<String>, CluResultInfo>(null);
938    
939            String courseResultType=CourseAssemblerConstants.COURSE_RESULT_TYPE_GRADE;
940            String resultsDescription="Grading options";
941            String resultDescription="Grading option";
942    
943            // Get the current options and put them in a map of option type id/cluResult
944            Map<String, ResultOptionInfo> currentResults = new HashMap<String, ResultOptionInfo>();
945    
946            CluResultInfo cluResult = null;
947    
948            //If this is not a create, lookup the results for this clu
949            if (!NodeOperation.CREATE.equals(operation)) {
950                for (CluResultInfo currentResult : currentCluResults) {
951                    if (courseResultType.equals(currentResult.getTypeKey())) {
952                        cluResult = currentResult;
953                        if(NodeOperation.DELETE.equals(operation)){
954                            //if this is a delete, then we only need the CluResultInfo
955                            cluResultNode.setOperation(NodeOperation.DELETE);
956                        }else{
957                            //Find all the Result options and store in a map for easy access later
958                            cluResultNode.setOperation(NodeOperation.UPDATE);
959                            for(ResultOptionInfo resultOption:currentResult.getResultOptions()){
960                                currentResults.put(resultOption.getResultComponentId(), resultOption);
961                            }
962                        }
963                        break;
964                    }
965                }
966            }
967    
968            //If this is a delete we don't need the result options, just the CluResultInfo
969            if(!NodeOperation.DELETE.equals(operation)){
970                if(cluResult == null){
971                    //Create a new resultInfo of the given type if one does not exist and set operation to Create
972                    cluResult = new CluResultInfo();
973                    cluResult.setCluId(cluId);
974                    cluResult.setStateKey(courseState);
975                    cluResult.setTypeKey(courseResultType);
976                    RichTextInfo desc = new RichTextInfo();
977                    desc.setPlain(resultsDescription);
978                    cluResult.setDescr(desc);
979                    cluResult.setEffectiveDate(new Date());
980                    cluResultNode.setOperation(NodeOperation.CREATE);
981                }
982    
983                cluResult.setResultOptions(new ArrayList<ResultOptionInfo>());
984    
985                // Loop through all the credit options in this course
986                for (String optionType : options) {
987                    if(currentResults.containsKey(optionType)){
988                        //If the option exists already copy it to the new list of result options
989                        ResultOptionInfo resultOptionInfo = currentResults.get(optionType);
990                        cluResult.getResultOptions().add(resultOptionInfo);
991                    }else{
992                        //Otherwise create a new result option
993                        ResultOptionInfo resultOptionInfo = new ResultOptionInfo();
994                        RichTextInfo desc = new RichTextInfo();
995                        desc.setPlain(resultDescription);
996                        resultOptionInfo.setDescr(desc);
997                        resultOptionInfo.setResultComponentId(optionType);
998                        resultOptionInfo.setStateKey(courseState);
999    
1000                        cluResult.getResultOptions().add(resultOptionInfo);
1001                    }
1002                }
1003            }
1004    
1005            cluResultNode.setNodeData(cluResult);
1006            return cluResultNode;
1007        }
1008    
1009        // TODO This is pretty much a copy of the FormatAssembler's
1010        // disassembleActivities code... maybe can be made generic
1011        private List<BaseDTOAssemblyNode<?, ?>> disassembleFormats(String nodeId,
1012                                                                   CourseInfo course, NodeOperation operation,ContextInfo contextInfo)
1013                throws AssemblyException, DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException, PermissionDeniedException {
1014    
1015            List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
1016    
1017            // Get the current formats and put them in a map of format id/relation
1018            // id
1019            Map<String, String> currentformatIds = new HashMap<String, String>();
1020    
1021            if (!NodeOperation.CREATE.equals(operation)) {
1022                try {
1023                    List<CluCluRelationInfo> formatRelationships = cluService
1024                            .getCluCluRelationsByClu(course.getId(),contextInfo);
1025    
1026                    formatRelationships = (null == formatRelationships) ? new ArrayList<CluCluRelationInfo>() : formatRelationships;
1027    
1028                    for (CluCluRelationInfo formatRelation : formatRelationships) {
1029                        if (CourseAssemblerConstants.COURSE_FORMAT_RELATION_TYPE
1030                                .equals(formatRelation.getTypeKey())) {
1031                            currentformatIds.put(formatRelation.getRelatedCluId(),
1032                                    formatRelation.getId());
1033                        }
1034                    }
1035                } catch (DoesNotExistException e) {
1036                } catch (InvalidParameterException e) {
1037                    throw new AssemblyException("Error getting related formats", e);
1038                } catch (MissingParameterException e) {
1039                    throw new AssemblyException("Error getting related formats", e);
1040                } catch (OperationFailedException e) {
1041                    throw new AssemblyException("Error getting related formats", e);
1042                }
1043            }
1044    
1045            // Loop through all the formats in this course
1046            for (FormatInfo format : course.getFormats()) {
1047    
1048                //  If this is a course create/new format update then all formats will be created
1049                if (NodeOperation.CREATE == operation
1050                        || (NodeOperation.UPDATE == operation && !currentformatIds.containsKey(format.getId()) )) {
1051                    // the format does not exist, so create
1052                    // Assemble and add the format
1053                    format.setStateKey(course.getStateKey());
1054                    BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1055                            .disassemble(format, NodeOperation.CREATE,contextInfo);
1056                    results.add(formatNode);
1057    
1058    
1059                    // Create the relationship and add it as well
1060                    CluCluRelationInfo relation = new CluCluRelationInfo();
1061                    relation.setCluId(nodeId);
1062                    relation.setRelatedCluId(formatNode.getNodeData().getId());// this
1063                    // should
1064                    // already
1065                    // be set even if
1066                    // it's a create
1067                    relation
1068                            .setTypeKey(CourseAssemblerConstants.COURSE_FORMAT_RELATION_TYPE);
1069                    relation.setStateKey(course.getStateKey());
1070    
1071                    BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo> relationNode = new BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo>(
1072                            null);
1073                    relationNode.setNodeData(relation);
1074                    relationNode.setOperation(NodeOperation.CREATE);
1075    
1076                    results.add(relationNode);
1077                } else if (NodeOperation.UPDATE == operation
1078                        && currentformatIds.containsKey(format.getId())) {
1079                    // If the course already has this format, then just update the
1080                    // format
1081                    format.setStateKey(course.getStateKey());
1082                    BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1083                            .disassemble(format, NodeOperation.UPDATE,contextInfo);
1084                    results.add(formatNode);
1085    
1086                    // remove this entry from the map so we can tell what needs to
1087                    // be deleted at the end
1088                    currentformatIds.remove(format.getId());
1089                } else if (NodeOperation.DELETE == operation
1090                        && currentformatIds.containsKey(format.getId()))  {
1091                    // Delete the Format and its relation
1092                    CluCluRelationInfo relationToDelete = new CluCluRelationInfo();
1093                    relationToDelete.setId( currentformatIds.get(format.getId()) );
1094                    BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo>(
1095                            null);
1096                    relationToDeleteNode.setNodeData(relationToDelete);
1097                    relationToDeleteNode.setOperation(NodeOperation.DELETE);
1098                    results.add(relationToDeleteNode);
1099    
1100                    BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1101                            .disassemble(format, NodeOperation.DELETE,contextInfo);
1102                    results.add(formatNode);
1103    
1104                    // remove this entry from the map so we can tell what needs to
1105                    // be deleted at the end
1106                    currentformatIds.remove(format.getId());
1107                }
1108            }
1109    
1110            // Now any leftover format ids are no longer needed, so delete
1111            // formats and relations. These formats have to be assembled first before they can be marked for deletion
1112            for (Entry<String, String> entry : currentformatIds.entrySet()) {
1113                // Create a new relation with the id of the relation we want to
1114                // delete
1115                CluCluRelationInfo relationToDelete = new CluCluRelationInfo();
1116                relationToDelete.setId( entry.getValue() );
1117                BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo>(
1118                        null);
1119                relationToDeleteNode.setNodeData(relationToDelete);
1120                relationToDeleteNode.setOperation(NodeOperation.DELETE);
1121                results.add(relationToDeleteNode);
1122    
1123                CluInfo formatCluToDelete = cluService.getClu(entry.getKey() , new ContextInfo());
1124                FormatInfo formatToDelete = formatAssembler.assemble(formatCluToDelete, null, false,contextInfo);
1125                BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1126                        .disassemble(formatToDelete, NodeOperation.DELETE,contextInfo);
1127                results.add(formatNode);
1128            }
1129    
1130            return results;
1131        }
1132    
1133        private List<CourseVariationInfo> assembleVariations(List<CluIdentifierInfo> cluIdents) {
1134            List<CourseVariationInfo> variations = new ArrayList<CourseVariationInfo>();
1135            if (cluIdents != null) {
1136                for (CluIdentifierInfo cluIdent : cluIdents) {
1137                    if (cluIdent.getTypeKey() != null &&
1138                            cluIdent.getTypeKey().equals(CourseAssemblerConstants.COURSE_VARIATION_IDENT_TYPE)) {
1139                        CourseVariationInfo variation = new CourseVariationInfo();
1140                        variation.setId(cluIdent.getId());
1141                        variation.setTypeKey(cluIdent.getTypeKey());
1142                        variation.setCourseNumberSuffix(cluIdent.getSuffixCode());
1143                        variation.setSubjectArea(cluIdent.getDivision());
1144                        variation.setVariationCode(cluIdent.getVariation());
1145                        variation.setVariationTitle(cluIdent.getLongName());
1146                        variations.add(variation);
1147                    }
1148                }
1149            }
1150            return variations;
1151        }
1152    
1153        private List<CourseCrossListingInfo> assembleCrossListings(List<CluIdentifierInfo> cluIdents)
1154                throws AssemblyException {
1155            List<CourseCrossListingInfo> crossListings = new ArrayList<CourseCrossListingInfo>();
1156            if (cluIdents != null) {
1157                for (CluIdentifierInfo cluIdent : cluIdents) {
1158                    if (cluIdent.getTypeKey() != null &&
1159                            cluIdent.getTypeKey().equals(CourseAssemblerConstants.COURSE_CROSSLISTING_IDENT_TYPE)) {
1160                        CourseCrossListingInfo crosslisting = new CourseCrossListingInfo();
1161    
1162                        boolean found = false;
1163                        for (AttributeInfo attr : cluIdent.getAttributes()){
1164                            if (attr.getKey().equals("courseId")){
1165                                found = true;
1166                                try {
1167                                    CluInfo cluInfo = cluService.getClu(attr.getValue(), new ContextInfo());
1168                                    crosslisting.setId(cluIdent.getId());
1169                                    crosslisting.setCode(cluInfo.getOfficialIdentifier().getCode());
1170                                    crosslisting.setAttributes(cluIdent.getAttributes());
1171                                    crosslisting.setTypeKey(cluInfo.getTypeKey());
1172                                    crosslisting.setCourseNumberSuffix(cluInfo.getOfficialIdentifier().getSuffixCode());
1173                                    crosslisting.setSubjectArea(cluInfo.getOfficialIdentifier().getDivision());
1174                                    crosslisting.setDepartment(cluIdent.getOrgId());
1175                                } catch (Exception e) {
1176                                    throw new AssemblyException("Error getting related clus", e);
1177                                }
1178                            }
1179                        }
1180                        if (!found) {
1181                            crosslisting.setId(cluIdent.getId());
1182                            crosslisting.setCode(cluIdent.getCode());
1183                            crosslisting.setAttributes(cluIdent.getAttributes());
1184                            crosslisting.setTypeKey(cluIdent.getTypeKey());
1185                            crosslisting.setCourseNumberSuffix(cluIdent.getSuffixCode());
1186                            crosslisting.setSubjectArea(cluIdent.getDivision());
1187                            crosslisting.setDepartment(cluIdent.getOrgId());
1188                        }
1189    
1190                        crossListings.add(crosslisting);
1191                    }
1192                }
1193            }
1194            return crossListings;
1195        }
1196    
1197        // TODO This is pretty much a copy of the disassembleJoints
1198        // code... maybe can be made generic
1199        private List<BaseDTOAssemblyNode<?, ?>> disassembleJoints(String nodeId,
1200                                                                  CourseInfo course, NodeOperation operation,ContextInfo contextInfo)
1201                throws AssemblyException, PermissionDeniedException {
1202    
1203            List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
1204    
1205            // Get the current joints and put them in a map of joint id/relation
1206            // id
1207            Map<String, CluCluRelationInfo> currentJointIds = new HashMap<String, CluCluRelationInfo>();
1208    
1209            if (!NodeOperation.CREATE.equals(operation)) {
1210                try {
1211                    List<CluCluRelationInfo> jointRelationships = cluService.getCluCluRelationsByClu(course.getId(),contextInfo);
1212                    for (CluCluRelationInfo jointRelation : jointRelationships) {
1213                        if (CourseAssemblerConstants.JOINT_RELATION_TYPE.equals(jointRelation.getTypeKey())) {
1214                            if(jointRelation.getCluId().equals(course.getId())) {
1215                                cluService.getClu(jointRelation.getRelatedCluId(), contextInfo);
1216                                currentJointIds.put(jointRelation.getId(),jointRelation);
1217                            } else {
1218                                cluService.getClu(jointRelation.getCluId(), contextInfo);
1219                                currentJointIds.put(jointRelation.getId(),jointRelation);
1220                            }
1221                        }
1222                    }
1223                } catch (DoesNotExistException e) {
1224                } catch (InvalidParameterException e) {
1225                    throw new AssemblyException("Error getting related formats", e);
1226                } catch (MissingParameterException e) {
1227                    throw new AssemblyException("Error getting related formats", e);
1228                } catch (OperationFailedException e) {
1229                    throw new AssemblyException("Error getting related formats", e);
1230                }
1231            }
1232    
1233            // Loop through all the joints in this course
1234            for (CourseJointInfo joint : course.getJoints()) {
1235                // If this is a course create then all joints will be created
1236                if (NodeOperation.UPDATE.equals(operation) && joint.getRelationId() != null
1237                        && currentJointIds.containsKey(joint.getRelationId())) {
1238                    // remove this entry from the map so we can tell what needs to
1239                    // be deleted at the end
1240                    CluCluRelationInfo relation = currentJointIds.remove(joint.getRelationId());
1241                    relation.setRelatedCluId(joint.getCourseId());
1242                    relation.setStateKey(course.getStateKey());
1243                    BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo> jointNode = new BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo>(courseJointAssembler);
1244                    jointNode.setBusinessDTORef(joint);
1245                    jointNode.setNodeData(relation);
1246                    jointNode.setOperation(NodeOperation.UPDATE);
1247                    results.add(jointNode);
1248                } else if (!NodeOperation.DELETE.equals(operation)) {
1249                    // the joint does not exist, so create cluclurelation
1250                    BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo> jointNode = courseJointAssembler
1251                            .disassemble(joint, NodeOperation.CREATE,contextInfo);
1252                    jointNode.getNodeData().setCluId(nodeId);
1253                    jointNode.getNodeData().setStateKey(course.getStateKey());
1254                    results.add(jointNode);
1255                }
1256            }
1257    
1258            // Now any leftover joint ids are no longer needed, so delete
1259            // joint relations
1260            for (String id : currentJointIds.keySet()) {
1261                // Create a new relation with the id of the relation we want to
1262                // delete
1263                CluCluRelationInfo relationToDelete = new CluCluRelationInfo();
1264                relationToDelete.setId(id);
1265                BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo>(
1266                        courseJointAssembler);
1267                relationToDeleteNode.setNodeData(relationToDelete);
1268                relationToDeleteNode.setOperation(NodeOperation.DELETE);
1269                results.add(relationToDeleteNode);
1270            }
1271    
1272            return results;
1273        }
1274    
1275        public void setCluService(CluService cluService) {
1276            this.cluService = cluService;
1277        }
1278    
1279        public void setFormatAssembler(FormatAssembler formatAssembler) {
1280            this.formatAssembler = formatAssembler;
1281        }
1282    
1283        public void setCourseJointAssembler(
1284                CourseJointAssembler courseJointAssembler) {
1285            this.courseJointAssembler = courseJointAssembler;
1286        }
1287    
1288        public void setLoAssembler(LoAssembler loAssembler) {
1289            this.loAssembler = loAssembler;
1290        }
1291    
1292        public void setLoService(LearningObjectiveService loService) {
1293            this.loService = loService;
1294        }
1295    
1296        public void setCluAssemblerUtils(CluAssemblerUtils cluAssemblerUtils) {
1297            this.cluAssemblerUtils = cluAssemblerUtils;
1298        }
1299    
1300        public void setLrcService(LRCService lrcService) {
1301            this.lrcService = lrcService;
1302        }
1303    
1304        public void setAtpService(AtpService atpService) {
1305            this.atpService = atpService;
1306        }
1307    
1308        public void setDefaultCreditIncrement(float defaultCreditIncrement) {
1309            this.defaultCreditIncrement = defaultCreditIncrement;
1310        }
1311    }